第七章:Handlers 处理器

学习如何使用 Handlers 实现配置变更后的服务重启。

最后更新: 2024-01-21
页面目录

Handlers 处理器

Handlers 是 Ansible 中用于响应任务执行结果的特殊任务,只有在被任务通知时才会执行。

Handler 基础

基本概念

┌─────────────────────────────────────────────────────────────────┐
│                      Handler 工作原理                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Task ──[ changed ]──► notify ──► Handler                       │
│                              │                                   │
│  Task ──[ ok ]────► (no notify)                                 │
│                              │                                   │
│  Handler 执行时机:                                                   │
│  1. 所有 tasks 执行完成后                                         │
│  2. 按定义的顺序执行(不是 notify 顺序)                           │
│  3. 每个 handler 只执行一次(无论被 notify 多少次)                 │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

简单示例

# handlers-basic.yml
---
- name: Configure Nginx
  hosts: webservers
  become: yes

  tasks:
    - name: Copy Nginx configuration
      copy:
        src: nginx.conf
        dest: /etc/nginx/nginx.conf
      notify: Reload Nginx

    - name: Start Nginx
      service:
        name: nginx
        state: started

  handlers:
    - name: Reload Nginx
      service:
        name: nginx
        state: reloaded

Handler 详细配置

基本结构

handlers:
  - name: Handler name
    module_name:
      module_argument: value
    listen: "handler topic"
    listen: "multiple topics"
    delay: 5
    retries: 3

Handler 参数

参数 说明
name Handler 名称,用于 notify 引用
listen 主题名称,多个 handler 可监听同一主题
listen 支持列表形式定义多个主题
delay 失败重试延迟(秒)
retries 失败重试次数

notify 通知机制

基本用法

tasks:
  # 单个 notify
  - name: Update config
    copy:
      src: app.conf
      dest: /etc/myapp/app.conf
    notify: Restart app

  # 多个 notify(都会被执行)
  - name: Update multiple configs
    copy:
      src: "{{ item.src }}"
      dest: "{{ item.dest }}"
    loop:
      - { src: 'nginx.conf', dest: '/etc/nginx/nginx.conf' }
      - { src: 'app.conf', dest: '/etc/myapp/app.conf' }
    notify:
      - Reload Nginx
      - Restart app

条件通知

tasks:
  - name: Update config
    copy:
      src: app.conf
      dest: /etc/myapp/app.conf
    notify: Restart app
    when: app_state == "running"

使用 listen 主题

tasks:
  # 使用主题名称通知
  - name: Update Nginx config
    copy:
      src: nginx.conf
      dest: /etc/nginx/nginx.conf
    notify: "Web services"

  - name: Update PHP config
    copy:
      src: php.ini
      dest: /etc/php/8.1/fpm/php.ini
    notify: "Web services"

handlers:
  # 监听主题的 handler
  - name: Reload Nginx
    service:
      name: nginx
      state: reloaded
    listen: "Web services"

  - name: Reload PHP-FPM
    service:
      name: php8.1-fpm
      state: reloaded
    listen: "Web services"

Handler 执行时机

执行顺序

---
- name: Handler execution order
  hosts: localhost
  connection: local

  tasks:
    - name: Task 1
      command: echo "Task 1"
      notify: Handler 1

    - name: Task 2
      command: echo "Task 2"
      notify: Handler 2

    - name: Task 3
      command: echo "Task 3"
      notify: Handler 1

  handlers:
    - name: Handler 1
      command: echo "Handler 1"
    
    - name: Handler 2
      command: echo "Handler 2"

执行结果:Handler 按定义顺序执行,而非 notify 顺序

  • Handler 1 将在 Handler 2 之前执行
  • Handler 1 只执行一次(即使被 notify 两次)

flush_handlers

---
- name: Flush handlers example
  hosts: localhost
  connection: local

  tasks:
    - name: Task 1
      command: echo "Task 1"
      notify: Handler 1
    
    - name: Flush handlers immediately
      meta: flush_handlers
    
    - name: Task 2
      command: echo "Task 2"
      notify: Handler 2

  handlers:
    - name: Handler 1
      command: echo "Handler 1"
    
    - name: Handler 2
      command: echo "Handler 2"

完整示例

多服务配置

# multi-service.yml
---
- name: Configure LAMP Stack
  hosts: webservers
  become: yes

  tasks:
    # Nginx 配置
    - name: Install Nginx
      apt:
        name: nginx
        state: present
      notify: Start Nginx

    - name: Copy Nginx config
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/sites-available/default
      notify: Reload Nginx

    # PHP-FPM 配置
    - name: Install PHP-FPM
      apt:
        name:
          - php8.1-fpm
          - php8.1-mysql
        state: present
      notify: Start PHP-FPM

    - name: Copy PHP-FPM config
      template:
        src: php-fpm.conf.j2
        dest: /etc/php/8.1/fpm/pool.d/www.conf
      notify: Restart PHP-FPM

    # Application 配置
    - name: Create app directory
      file:
        path: /var/www/html
        state: directory
        owner: www-data
        group: www-data

    - name: Deploy application
      copy:
        src: app/
        dest: /var/www/html/
        owner: www-data
        group: www-data
      notify:
        - Reload Nginx
        - Restart PHP-FPM

  handlers:
    - name: Start Nginx
      service:
        name: nginx
        state: started
        enabled: yes

    - name: Reload Nginx
      service:
        name: nginx
        state: reloaded

    - name: Start PHP-FPM
      service:
        name: php8.1-fpm
        state: started
        enabled: yes

    - name: Restart PHP-FPM
      service:
        name: php8.1-fpm
        state: restarted

数据库主从配置

# database-cluster.yml
---
- name: Configure MySQL Cluster
  hosts: dbservers
  become: yes
  serial: 1  # 逐台执行

  tasks:
    - name: Install MySQL
      apt:
        name: mysql-server
        state: present

    - name: Copy MySQL config
      template:
        src: my.cnf.j2
        dest: /etc/mysql/my.cnf
      notify: Restart MySQL

    - name: Setup replication
      include_tasks: setup_replication.yml
      when: inventory_hostname in groups['dbservers_slave']
      notify:
        - Start slave
        - Check replication

  handlers:
    - name: Restart MySQL
      service:
        name: mysql
        state: restarted

    - name: Start slave
      command: mysql -e "START SLAVE;"

    - name: Check replication
      command: mysql -e "SHOW SLAVE STATUS\G"
      register: slave_status

    - name: Notify monitoring
      debug:
        msg: "Replication status: {{ slave_status.stdout }}"
      listen: "Monitoring alerts"

Handler 高级特性

Handler 条件执行

handlers:
  - name: Restart app
    service:
      name: myapp
      state: restarted
    when: app_state == "running"

Handler 变量

vars:
  nginx_notify_topic: "Reload services"

tasks:
  - name: Update config
    copy:
      src: app.conf
      dest: /etc/myapp/app.conf
    notify: "{{ nginx_notify_topic }}"

handlers:
  - name: Reload services
    service:
      name: "{{ item }}"
      state: reloaded
    loop:
      - nginx
      - php-fpm
    listen: "Reload services"

Handler 中的错误处理

handlers:
  - name: Restart service
    service:
      name: nginx
      state: restarted
    listen: Restart service

  - name: Rollback on failure
    command: /opt/rollback.sh
    listen: Restart service
    failed_when: false
    when: ansible_facts['ansible_distribution'] == "Ubuntu"

Handler 最佳实践

1. 使用清晰的命名

# 推荐
handlers:
  - name: Restart nginx
  - name: Reload php-fpm
  - name: Restart mysql

# 不推荐
handlers:
  - name: handler1
  - name: restart

2. 使用 listen 分组

tasks:
  - name: Update nginx config
    copy:
      src: nginx.conf
      dest: /etc/nginx/nginx.conf
    notify: "Web server reload"

  - name: Update php config
    copy:
      src: php.ini
      dest: /etc/php/8.1/fpm/php.ini
    notify: "Web server reload"

handlers:
  - name: Reload nginx
    service:
      name: nginx
      state: reloaded
    listen: "Web server reload"

  - name: Reload php-fpm
    service:
      name: php8.1-fpm
      state: reloaded
    listen: "Web server reload"

3. Handler 放在 Play 底部

---
- name: Recommended structure
  hosts: webservers
  
  tasks:
    - name: Task 1
      copy:
        src: file1.conf
        dest: /etc/
      notify: Handler 1
  
  handlers:
    - name: Handler 1
      service:
        name: myapp
        state: restarted

4. 使用 force_handlers 确保执行

---
- name: Ensure handlers run even on failure
  hosts: webservers
  force_handlers: yes
  
  tasks:
    - name: Install package
      apt:
        name: custom-package
        state: present
      
    - name: This will fail
      command: /bin/false
    
    - name: Config change
      copy:
        src: app.conf
        dest: /etc/myapp/app.conf
      notify: Restart myapp
  
  handlers:
    - name: Restart myapp
      service:
        name: myapp
        state: restarted

常见问题

Handler 不执行

  1. 检查任务是否真正改变了状态
  2. 检查 notify 名称是否与 handler name 完全匹配
  3. 使用 -v 查看详细输出

Handler 执行顺序不对

Handler 按定义顺序执行,而非 notify 顺序。使用 listen 可以创建逻辑分组。

Handler 执行多次

每个 handler 只执行一次,无论被 notify 多少次。

下一步

现在你已经掌握了 Handlers 的使用。接下来让我们学习变量和 Facts。

👉 变量与 Facts