Skip to content

ansible-playbook

Playbook 是 Ansible 的核心功能,使用 YAML 格式编写,可以定义复杂的工作流程

语法结构

bash
ansible-playbook playbook.yml

参数

参数简写说明经典案例
--inventory-i指定库存文件路径ansible-playbook -i hosts.ini site.yml
--list-hosts-列出匹配的主机ansible-playbook play.yml --list-hosts
--list-tasks-列出将要执行的任务ansible-playbook deploy.yml --list-tasks
--list-tags-列出可用标签ansible-playbook setup.yml --list-tags
--limit-限制执行到特定主机ansible-playbook play.yml --limit web01
--start-at-task-从指定任务开始执行ansible-playbook rollback.yml --start-at-task="stop service"
--step-交互式逐步执行ansible-playbook critical.yml --step
--check-C模拟执行(dry-run)ansible-playbook change.yml --check
--tags-t只运行指定标签任务ansible-playbook deploy.yml --tags=config
--skip-tags-跳过指定标签任务ansible-playbook deploy.yml --skip-tags=test
--user-u指定SSH用户ansible-playbook play.yml -u admin
--ask-pass-k提示输入SSH密码ansible-playbook play.yml -k
--private-key-指定私钥文件ansible-playbook play.yml --private-key=~/.ssh/deploy_key
--ask-become-pass-K提示输入特权密码ansible-playbook play.yml -K
--become-b启用特权升级ansible-playbook play.yml --become
--become-method-指定特权方法ansible-playbook play.yml --become-method=su
--become-user-指定特权用户ansible-playbook play.yml --become-user=oracle
--verbose-v详细输出模式ansible-playbook debug.yml -vvv
--syntax-check-只检查语法ansible-playbook new.yml --syntax-check
--extra-vars-e传递额外变量ansible-playbook deploy.yml --extra-vars "version=2.4"
--vault-id-指定vault身份ansible-playbook secret.yml --vault-id @prompt
--flush-cache-清除fact缓存ansible-playbook play.yml --flush-cache
--force-handlers-强制运行handlersansible-playbook play.yml --force-handlers

查看详细日志

bash
ansible-playbook playbook.yml -vvv  # 查看详细日志

Playbook 结构

yaml
---
# Playbook 基础定义
- name: Playbook描述                  # Playbook名称(必填)
  hosts: target_hosts                # 目标主机/组(必填)
  gather_facts: true                 # 是否开启内置变量(默认true)
  become: yes                        # 是否提权(默认no)
  become_user: root                  # 提权用户(默认root)
  vars:                              # 定义变量
    http_port: 80
    db_host: "db01.example.com"
  vars_files:                        # 引用外部变量文件
    - vars/common.yml
  vars_prompt:                       # 运行时交互式输入变量
    - name: "db_password"
      prompt: "Enter DB password"
      private: yes

  # Pre-tasks(在roles之前执行的任务)
  pre_tasks:
    - name: 前置任务示例
      debug:
        msg: "正在执行初始化..."

  # Handlers(由notify触发)
  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

  # Roles 调用
  roles:
    - role: base_setup               # 基础角色
      vars:                          # 角色专用变量
        timezone: "Asia/Shanghai"
    - { role: web_server, tags: web } # 简写格式

  # Tasks(主任务列表)
  tasks:
    - name: 安装Nginx                # 任务名称(必填)
      apt:                           # 模块名称(必填)
        name: nginx
        state: latest
      tags: install                 # 任务标签
      when: ansible_os_family == "Debian" # 条件判断
      notify: restart nginx         # 触发handler
      register: install_result      # 注册变量
      retries: 3                    # 重试次数
      delay: 10                     # 重试间隔(秒)
      ignore_errors: yes            # 忽略错误
      changed_when: false           # 强制不标记为changed
      async: 3600                   # 异步任务超时时间
      poll: 30                      # 异步轮询间隔

    - name: 使用循环
      copy:
        src: "{{ item.src }}"
        dest: "{{ item.dest }}"
      loop:                         # 循环结构
        - { src: file1, dest: /tmp/ }
        - { src: file2, dest: /opt/ }
      loop_control:                 # 循环控制
        label: "{{ item.src }}"     # 简化输出显示

  # Post-tasks(在roles之后执行的任务)
  post_tasks:
    - name: 清理临时文件
      file:
        path: "/tmp/{{ app_name }}"
        state: absent

  # 块结构(错误处理)
  tasks:
    - block:                        # 块开始
        - name: 高风险操作
          shell: /opt/scripts/dangerous.sh
      rescue:                       # 错误处理
        - name: 错误恢复
          debug:
            msg: "操作失败,已回滚"
      always:                       # 始终执行
        - name: 清理资源
          file:
            path: /tmp/lockfile
            state: absent

  # Tags 全局定义
  tags:
    - always                        # 始终执行
    - never                         # 跳过执行(需--skip-tags触发)
  • 执行顺序:收集facts → pre*tasks → roles → tasks → post*tasks → handlers
  • 特殊标记
    • tags: 允许选择性执行任务(--tags/--skip-tags
    • when: 条件判断(支持Jinja2表达式)
    • register: 保存任务结果供后续使用
    • block/rescue/always: 异常处理三件套

核心组件说明

组件说明示例
hosts目标主机组webservers
vars变量定义http_port: 80
tasks任务列表见下文示例
handlers触发器notify: restart apache
roles角色引用roles: [common, web]

关键字

Ansible Playbook 使用 YAML 格式编写,包含多个关键字用于定义配置和自动化任务。

核心结构关键字

关键字说明示例
hosts指定目标主机或组hosts: webservers
tasks定义要执行的任务列表tasks:
- name: Install package
vars定义变量vars:
http_port: 80
handlers定义处理器(触发执行的任务)handlers:
- name: restart nginx
become启用特权升级become: yes
become_user指定特权用户become_user: root
become_method指定特权升级方法become_method: sudo

任务控制关键字

关键字说明示例
name任务或块的描述性名称name: Install Nginx
when条件执行when: ansible_os_family == "Debian"
register保存任务输出到变量register: result
ignore_errors忽略任务错误ignore_errors: yes
changed_when自定义"changed"状态changed_when: false
failed_when自定义"failed"状态failed_when: "'ERROR' in command_result.stdout"
loop循环执行任务loop: ""
until重试直到条件满足until: result.rc == 0
retries重试次数retries: 3
delay重试间隔(秒)delay: 10

模块相关关键字

关键字说明示例
action指定模块和参数action: copy src=a.txt dest=/tmp
local_action在控制节点上执行local_action: command echo hello
delegate_to委托任务到特定主机delegate_to: 192.168.1.100
run_once只运行一次run_once: yes

变量和事实关键字

关键字说明示例
set_fact设置事实变量set_fact:
my_var: "value"
include_vars从文件加载变量include_vars:
file: vars.yml
vars_files包含变量文件vars_files:
- secrets.yml
facts是否收集事实gather_facts: no

组织和复用关键字

关键字说明示例
include_tasks包含任务文件include_tasks: setup.yml
import_tasks静态导入任务文件import_tasks: install.yml
roles包含角色roles:
- common
- webserver
include_role动态包含角色include_role:
name: mysql
import_role静态导入角色import_role:
name: nginx
block任务分组block:
- name: Task 1
- name: Task 2
rescue错误处理块rescue:
- debug:
msg: "Recovered from error"
always总是执行的块always:
- debug:
msg: "This always runs"

高级控制关键字

关键字说明示例
tags为任务打标签tags:
- install
- config
no_log隐藏任务输出no_log: true
async异步执行async: 60
poll异步轮询间隔poll: 5
throttle限制并发数throttle: 5
serial滚动更新批次大小serial: 2

任务(Tasks)编写规范

基本结构

yaml
tasks:
  - name: 描述性任务名称        # 任务描述(必填)
    module_name:               # 模块名
      param1: value1           # 模块参数
      param2: value2
    register: result_var       # 保存任务输出到变量
    when: condition            # 条件执行
    ignore_errors: yes|no      # 是否忽略错误
    tags:                      # 任务标签
      - tag1
    become: yes|no             # 是否提权
    become_user: root          # 提权用户
    loop:                      # 循环
      - item1
      - item2
    retries: 3                 # 重试次数
    delay: 10                  # 重试间隔(秒)

变量管理

变量定义方式

vars 自定义变量
yaml
vars:
  http_port: 80
  server_name: "example.com"
vars_files 引入外部变量文件
yaml
vars_files:
  - vars/common.yml
  - vars/secrets.yml  # 通常用于存放敏感信息
在 Inventory 文件中定义
ini
[webservers]
web1.example.com http_port=8080
web2.example.com

[webservers:vars]
server_name="webcluster.example.com"
使用 include_vars 动态加载
yaml
tasks:
  - name: 加载变量文件
    include_vars: "vars/{{ env }}.yml"
通过命令行传递变量
bash
ansible-playbook playbook.yml --extra-vars "http_port=8080"
set_fact 临时变量
yaml
tasks:
  - name: 设置临时变量
    set_fact:
      temp_var: "临时值"

变量优先级顺序

Ansible 变量遵循特定的优先级顺序(从低到高):

  1. 命令行值 (--extra-vars)
  2. 角色默认值 (roles/role/defaults/main.yml)
  3. Inventory 文件或脚本组变量
  4. Inventory 文件或脚本主机变量
  5. Playbook 中的 vars 定义
  6. Playbook 中的 vars_files 定义
  7. 注册变量 (register)
  8. set_fact 设置的变量
  9. 角色变量 (roles/role/vars/main.yml)
  10. 块变量 (block 中的 vars)
  11. 任务变量 (task 中的 vars)

变量类型

简单变量
yaml
app_name: "MyApp"
version: 1.0

列表/数组变量

yaml
packages:
  - nginx
  - mysql-server
  - php-fpm
字典/哈希变量
yaml
user_info:
  name: "john"
  uid: 1001
  groups: "admin,wheel"
布尔变量
yaml
enable_feature: true
debug_mode: no

变量使用方式

基本引用
yaml
tasks:
  - name: 使用变量
    debug:
      msg: "服务器端口是 {{ http_port }}"
访问字典变量
yaml
user_name: "{{ user_info.name }}"
访问列表变量
yaml
first_package: "{{ packages[0] }}"
变量过滤器
yaml
lowercase_name: "{{ app_name | lower }}"
Ansible 内置变量
yaml
---
- hosts: 主机群
    # 启用ansible 内置变量(setup 模块中的变量名)
    gather_facts:  True
    tasks:
      - name: 显示主机IP
        debug:
          msg: "主机IP是 {{ ansible_default_ipv4.address }}"
注册变量 (register)
yaml
tasks:
  - name: 检查服务状态
    command: systemctl status nginx
    register: nginx_status					# 将执行结果注册到变量中

  - name: 显示结果
    debug:
      var: nginx_status.stdout				# 调用变量输出结果

完整示例

yaml
---
- name: 变量使用示例
  hosts: webservers
  become: yes
  # 启用ansible 内置变量(setup 模块中的变量名)
  gather_facts:  True
  vars:
    app_name: "MyWebApp"
    app_version: "1.2.3"
    ports:
      http: 80
      https: 443
    users:
      - name: "admin"
        role: "superuser"
      - name: "guest"
        role: "readonly"
  
  vars_files:
    - vars/db_settings.yml
  
  tasks:
    - name: 显示应用信息
      debug:
        msg: "正在部署 {{ app_name }} 版本 {{ app_version }}"
    
    - name: 确保用户存在
      user:
        name: "{{ item.name }}"
        groups: "{{ item.role }}"
      loop: "{{ users }}"
    
    - name: 检查数据库连接
      command: "mysql -h {{ db_host }} -u {{ db_user }} -p{{ db_password }} -e 'SHOW DATABASES'"
      register: db_check
      ignore_errors: yes
    
    - name: 显示检查结果
      debug:
        msg: "数据库连接 {{ '成功' if db_check.rc == 0 else '失败' }}"
    
    - name: 设置动态端口
      set_fact:
        dynamic_port: "{{ ports.http + 1000 }}"
    
    - name: 使用动态端口
      debug:
        msg: "动态端口是 {{ dynamic_port | default('未设置') }}"

处理程序(Handlers)字段

Handlers 是 Ansible 中一种特殊的任务,用于在 playbook 中实现"触发-执行"机制。它们通常用于服务重启、配置重载等需要在其他任务完成后执行的操作。

基本概念

Handlers 具有以下特点:

  • 由普通任务通过 notify 触发
  • 只在所有普通任务执行完毕后运行
  • 即使被多次通知,也只会执行一次
  • 按照定义的顺序执行

基本语法

yaml
handlers:
  - name: <handler名称>
    <模块>: <参数>

核心关键字

notify - 触发处理程序

在任务中使用 notify 来触发 handler:

yaml
tasks:
  - name: 复制配置文件
    copy:
      src: files/nginx.conf
      dest: /etc/nginx/nginx.conf
    notify: restart nginx  # 触发名为"restart nginx"的handler

handlers:
  - name: restart nginx
listen - 监听组事件(Ansible 2.2+)

允许多个任务触发同一组 handlers:

yaml
handlers:
  - name: restart services
    listen: "restart web services"
    service:
      name: "{{ item }}"
      state: restarted
    loop: [nginx, apache2]

触发方式:

yaml
tasks:
  - name: 更新配置
    template:
      src: template.j2
      dest: /etc/app.conf
    notify: "restart web services"		# 触发listen名为“restart web services”的handler
完整示例
yaml
---
- name: Web服务器配置示例
  hosts: webservers
  become: yes

  vars:
    app_port: 8080

  tasks:
    - name: 安装Nginx
      apt:
        name: nginx
        state: latest
      notify: enable nginx  # 触发enable handler

    - name: 配置Nginx
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify:
        - reload nginx  # 触发reload handler
        - "restart web services"  # 触发listen组

    - name: 配置防火墙
      ufw:
        rule: allow
        port: "{{ app_port }}"
      notify: "restart web services"

    - name: 确保所有handlers立即执行
      meta: flush_handlers

  handlers:
    - name: enable nginx
      service:
        name: nginx
        enabled: yes

    - name: reload nginx
      service:
        name: nginx
        state: reloaded

    - name: restart all services
      listen: "restart web services"
      service:
        name: "{{ item }}"
        state: restarted
      loop:
        - nginx
        - ufw

条件判断

条件判断是 Ansible Playbook 中实现逻辑控制的核心功能,它允许您基于变量、事实或任务执行结果来决定是否执行特定任务或 play。

基础条件判断

when 语句基本用法
yaml
tasks:
  - name: 只在Ubuntu系统上执行
    apt:
      name: nginx
      state: latest
    when: ansible_distribution == "Ubuntu"
多条件组合
yaml
tasks:
  - name: 多条件判断
    command: /usr/bin/do_something
    when:
      - ansible_distribution == "CentOS"
      - ansible_distribution_major_version == "7"
      - inventory_hostname in groups['web_servers']

常用条件表达式

变量存在性检查
yaml
when: my_var is defined           # 变量已定义
when: my_var is not defined       # 变量未定义
when: my_var is none              # 变量为null
布尔值判断
yaml
when: enable_feature             # 等同于 == True
when: not disable_service        # 否定判断
字符串操作
yaml
when: "'success' in command_result.stdout"  # 包含字符串
when: db_host == 'localhost'     # 字符串相等
when: path.startswith('/opt')    # 字符串开头
数值比较
yaml
when: ansible_memtotal_mb > 4096  # 内存大于4GB
when: cpu_cores|int >= 8          # 使用过滤器转换类型
列表操作
yaml
when: item in list_of_items      # 包含检查
when: 80 in ansible_all_ipv4_addresses  # 检查IP列表
注册变量 + 条件判断
yaml
tasks:
  - name: 检查文件是否存在
    stat:
      path: /etc/nginx/nginx.conf
    register: nginx_conf

  - name: 如果文件存在则备份
    copy:
      src: /etc/nginx/nginx.conf
      dest: /backup/nginx.conf.bak
    when: nginx_conf.stat.exists
条件与循环结合
yaml
tasks:
  - name: 只为运行中的容器执行操作
    docker_container_info:
      name: "{{ item }}"
    register: containers
    loop: "{{ container_list }}"

  - name: 重启运行中的容器
    docker_container:
      name: "{{ item.item }}"
      state: restarted
    loop: "{{ containers.results }}"
    when: item.State.Status == "running"
条件与错误处理
yaml
tasks:
  - name: 尝试危险操作
    command: /usr/bin/risky_command
    register: cmd_result
    ignore_errors: yes

  - name: 失败时执行恢复
    command: /usr/bin/recovery
    when: cmd_result.rc != 0

条件过滤器

default 过滤器
yaml
when: port_number | default(8080) > 1024
bool 过滤器
yaml
when: user_input | bool  # 将字符串转为布尔值
match 过滤器
yaml
when: ansible_hostname is match('web-.*')  # 正则匹配

特殊条件应用

基于标签的条件执行
yaml
tasks:
  - name: 生产环境特定任务
    command: /usr/bin/prod_only
    tags: production
    when: "'production' in group_names"
基于事实的条件
yaml
tasks:
  - name: 只在虚拟机上执行
    command: /usr/bin/vm_tool
    when: ansible_virtualization_type != 'physical'
多play条件控制
yaml
- hosts: all
  tasks: [...]

- hosts: db_servers
  when: "'database' in group_names"
  tasks: [...]

完整示例

yaml
---
- name: 条件判断综合示例
  hosts: all
  vars:
    backup_enabled: true
    service_ports: [80, 443, 8080]
    min_memory: 2048

  tasks:
    - name: 收集系统信息
      setup:
        filter: "ansible_*"
      tags: always

    - name: 检查内存是否充足
      debug:
        msg: "内存充足 ({{ ansible_memtotal_mb }}MB)"
      when: ansible_memtotal_mb >= min_memory

    - name: 备份配置文件
      copy:
        src: "/etc/{{ item }}"
        dest: "/backup/{{ item }}"
      with_items:
        - nginx.conf
        - hosts
      when:
        - backup_enabled | default(false)
        - ansible_os_family == "Debian"

    - name: 验证端口开放
      wait_for:
        port: "{{ item }}"
        timeout: 2
      loop: "{{ service_ports }}"
      ignore_errors: yes
      register: port_checks
      when: "'web_servers' in group_names"

    - name: 报告端口状态
      debug:
        msg: "端口 {{ item.item }} {{ '开放' if item.rc == 0 else '关闭' }}"
      loop: "{{ port_checks.results }}"
      loop_control:
        label: "Port {{ item.item }}"

循环控制

基础循环(with_items)

yaml
- name: 安装多个软件包
  yum:
    name: "{{ item }}"
    state: present
  with_items:
    - nginx
    - mysql-server
    - php-fpm

字典列表循环

yaml
- name: 创建多个用户
  user:
    name: "{{ item.name }}"
    uid: "{{ item.uid }}"
    groups: "{{ item.groups }}"
  with_items:
    - { name: 'user1', uid: 1001, groups: 'wheel' }
    - { name: 'user2', uid: 1002, groups: 'users' }

文件列表循环

yaml
- name: 复制多个配置文件
  copy:
    src: "{{ item.src }}"
    dest: "{{ item.dest }}"
    mode: 0644
  with_files:
    - { src: 'file1.conf', dest: '/etc/file1.conf' }
    - { src: 'file2.conf', dest: '/etc/file2.conf' }

目录内容循环

yaml
- name: 处理目录下所有文件
  template:
    src: "{{ item }}"
    dest: "/etc/{{ item | basename }}"
  with_fileglob:
    - "templates/*.j2"

数字范围循环

yaml
- name: 创建序列目录
  file:
    path: "/data/volume{{ item }}"
    state: directory
  with_sequence: start=1 end=5

条件循环控制

yaml
- name: 选择性服务重启
  service:
    name: "{{ item.name }}"
    state: restarted
  with_items: "{{ services }}"
  when: item.restart_required

嵌套循环实现

yaml
- name: 配置多应用多环境
  template:
    src: "{{ item.app }}/{{ item.env }}.j2"
    dest: "/etc/{{ item.app }}/config.conf"
  with_nested:
    - [ 'app1', 'app2' ]
    - [ 'dev', 'prod' ]

循环结果注册

yaml
- name: 批量检查服务状态
  command: systemctl status {{ item }}
  register: service_status
  with_items:
    - nginx
    - mysql
    - redis

- name: 显示检查结果
  debug:
    msg: "{{ item.item }} 状态: {{ '运行中' if item.rc == 0 else '未运行' }}"
  with_items: "{{ service_status.results }}"

循环优化技巧

  1. 使用loop_control控制输出显示:
yaml
  loop_control:
    label: "Processing {{ item.name }}"
  1. 限制循环并发数:
yaml
  throttle: 3
  1. 跳过特定项:
yaml
  when: item != 'skip_this'
  1. 使用include_tasks实现动态循环:
yaml
- name: 动态包含任务文件
  include_tasks: process_item.yml
  with_items: "{{ dynamic_list }}"

错误处理

基础错误忽略

yaml
- name: 尝试危险操作(基础忽略)
  command: /usr/bin/risky-command
  ignore_errors: yes  # 忽略所有错误继续执行
  register: cmd_result

- name: 显示结果(无论成功失败)
  debug:
    msg: "命令执行完成,状态码: {{ cmd_result.rc }}"

条件错误处理

yaml
- name: 检查服务状态
  command: systemctl status nginx
  register: service_status
  failed_when: false  # 永不标记为失败
  changed_when: false # 永不标记为变更

- name: 根据状态码处理
  command: systemctl restart nginx
  when: service_status.rc != 0  # 仅当状态检查失败时执行

块级错误处理

yaml
- block:  # 定义可能出错的代码块
    - name: 更新关键配置
      template:
        src: config.j2
        dest: /etc/app.conf
      
    - name: 触发配置重载
      command: /usr/bin/reload-config
  
  rescue:  # 出错时执行的恢复逻辑
    - name: 恢复备份配置
      copy:
        src: /backup/app.conf.bak
        dest: /etc/app.conf
      
    - name: 发送告警通知
      mail:
        subject: "配置更新失败"
        body: "主机 {{ inventory_hostname }} 配置更新失败"
  
  always:  # 无论成功失败都执行
    - name: 记录执行日志
      lineinfile:
        path: /var/log/ansible.log
        line: "配置更新尝试于 {{ ansible_date_time.iso8601 }}"

自定义失败条件

yaml
- name: 检查磁盘空间
  command: df -h /
  register: disk_usage
  failed_when:  # 自定义失败条件
    - "'No such file' in disk_usage.stderr"  # 命令执行错误
    - "'90%' in disk_usage.stdout"          # 磁盘使用超过90%
  changed_when: false  # 纯检查任务不触发变更

- name: 磁盘清理任务
  command: /usr/bin/clean-disk
  when: disk_usage.failed  # 仅在检查失败时执行

重试机制实现

yaml
- name: 等待服务启动
  uri:
    url: "http://localhost:8080/health"
    method: GET
  register: health_check
  until: health_check.status == 200  # 成功条件
  retries: 5      # 最大重试次数
  delay: 10       # 每次间隔秒数
  ignore_errors: yes  # 最终失败也不终止playbook

- name: 显示最终状态
  debug:
    msg: "服务{{ '已启动' if health_check.status == 200 else '启动失败' }}"

错误传播控制

yaml
- name: 多步骤任务组
  block:
    - name: 步骤一(关键)
      command: /bin/step1
      
    - name: 步骤二(可跳过)
      command: /bin/step2
      ignore_errors: yes
      
    - name: 步骤三(关键)
      command: /bin/step3
  
  rescue:
    - name: 错误处理
      debug:
        msg: "关键步骤失败,跳过后续任务"
      meta: clear_host_errors  # 清除错误状态继续执行其他host

综合处理案例

yaml
- name: 应用部署流程
  hosts: app_servers
  vars:
    max_attempts: 3
    backoff_delay: 5
  
  tasks:
    - name: 预检查环境
      block:
        - name: 验证磁盘空间
          command: df -h /
          register: disk_check
          failed_when: "'90%' in disk_check.stdout"
          
        - name: 验证内存
          command: free -m
          register: mem_check
          failed_when: "'available' not in mem_check.stdout"
      
      rescue:
        - name: 环境检查失败处理
          debug:
            msg: "主机 {{ inventory_hostname }} 不满足部署要求"
          meta: end_play  # 终止当前play

    - name: 分阶段部署
      block:
        - name: 传输安装包
          copy:
            src: "/dist/{{ app_package }}"
            dest: "/tmp/"
          
        - name: 安装应用
          command: "/bin/install-app /tmp/{{ app_package }}"
          register: install_result
          retries: "{{ max_attempts }}"
          delay: "{{ backoff_delay }}"
          until: install_result.rc == 0
          
        - name: 验证安装
          uri:
            url: "http://localhost:8080/status"
            method: GET
          register: verify_result
          failed_when: verify_result.json.status != 'OK'
      
      rescue:
        - name: 记录失败信息
          lineinfile:
            path: "/var/log/deploy_errors.log"
            line: "{{ inventory_hostname }} 失败于 {{ ansible_date_time.iso8601 }}"
            
        - name: 执行回滚
          include_tasks: rollback.yml
          when: "'install_result' in vars"  # 仅在安装阶段后失败时执行
      
      always:
        - name: 清理临时文件
          file:
            path: "/tmp/{{ app_package }}"
            state: absent

包含(Includes)

静态包含(import_tasks)

yaml
- name: 主Playbook
  hosts: webservers
  tasks:
    # 静态包含会在解析时立即加载(变量提前解析)
    - name: 包含基础配置任务
      import_tasks: tasks/base_setup.yml  # 文件路径相对playbook所在目录
      vars:  # 可传递变量到被包含文件
        install_dir: /opt/app
      tags:  # 标签会传递给所有被包含任务
        - setup
        - base

# tasks/base_setup.yml
- name: 创建安装目录  # 此任务会自动继承tags和vars
  file:
    path: "{{ install_dir }}"
    state: directory

动态包含(include_tasks)

yaml
- name: 主Playbook
  hosts: appservers
  tasks:
    # 动态包含在运行时加载(变量实时计算)
    - name: 包含环境特定任务
      include_tasks: "tasks/{{ env_type }}_setup.yml"  # 可使用变量指定文件
      when: env_type is defined  # 条件判断在包含前执行
      loop:  # 支持循环包含
        - dev
        - prod
      loop_control:
        loop_var: env_type  # 重定义循环变量名

角色包含(import_role/include_role)

yaml
- name: 主Playbook
  hosts: all
  tasks:
    # 静态角色导入(解析时加载)
    - name: 导入基础角色
      import_role:
        name: common  # 角色名称
        tasks_from: install.yml  # 指定执行的入口文件
      vars:
        java_version: 11
    
    # 动态角色包含(运行时加载)
    - name: 包含环境角色
      include_role:
        name: "{{ env_type }}_config"  # 支持变量
        apply:  # 可应用额外参数
          tags:
            - config
          when: not skip_config

文件包含(include_vars)

yaml
- name: 主Playbook
  hosts: dbservers
  tasks:
    # 包含变量文件(YAML/JSON格式)
    - name: 加载数据库配置
      include_vars:
        file: vars/db_config.yml  # 显式指定文件
        name: db_config  # 将变量存入指定命名空间
        depth: 1  # 控制嵌套变量层级
    
    - name: 使用包含的变量
      debug:
        msg: "DB端口: {{ db_config.port }}"

条件包含案例

yaml
- name: 智能部署流程
  hosts: clusters
  vars:
    components: ["frontend", "backend", "database"]
  
  tasks:
    # 根据组件类型动态包含
    - name: 包含组件部署逻辑
      include_tasks: "tasks/deploy_{{ item }}.yml"
      loop: "{{ components }}"
      when: "deploy_{{ item }}"  # 检查对应布尔变量
    
    # 错误处理包含
    - name: 包含监控部署
      block:
        - include_tasks: tasks/deploy_monitor.yml
      rescue:
        - include_tasks: tasks/fallback_monitor.yml

嵌套包含案例

yaml
# playbooks/main.yml
- name: 基础设施部署
  hosts: "{{ target }}"
  tasks:
    - import_playbook: plays/network.yml  # 包含整个playbook
    - include_tasks: tasks/security_groups.yml
      tags: security

# plays/network.yml
- name: 网络配置
  hosts: "{{ target }}"
  vars:
    network_profile: "default"
  tasks:
    - include_tasks: "network/{{ network_profile }}.yml"

模板与文件管理

Jinja2 模板基础

yaml
- name: 生成Nginx配置
  template:
    src: templates/nginx.conf.j2  # 模板文件路径
    dest: /etc/nginx/nginx.conf  # 目标路径
    owner: root                 # 文件属主
    group: root                 # 文件属组
    mode: '0644'                # 文件权限
    backup: yes                 # 修改前自动备份
  notify: restart nginx         # 触发handler

模板变量使用

ini
# templates/db.conf.j2
[database]
host = {{ db_host | default('localhost') }}  # 使用带默认值的变量
port = {{ db_port }}
{% if db_ssl %}                              # 条件判断
ssl = true
ssl_cert = /etc/ssl/{{ cert_file }}
{% endif %}

文件内容管理

yaml
- name: 确保文件存在
  file:
    path: /etc/app_config.json
    state: touch               # 创建空文件
    owner: appuser
    group: appgroup

- name: 修改文件内容
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^#?Port'          # 匹配行
    line: 'Port 2222'          # 替换内容
    backup: yes                # 修改前备份

目录管理

yaml
- name: 创建日志目录结构
  file:
    path: "/var/log/{{ app_name }}/{{ item }}"
    state: directory           # 创建目录
    mode: '0755'
  with_items:                  # 循环创建
    - archive
    - audit
    - debug

文件分发

yaml
- name: 分发静态文件
  copy:
    src: files/static/          # 源目录
    dest: /var/www/static/      # 目标目录
    owner: www-data
    group: www-data
    mode: '0644'
    remote_src: no             # 从控制节点复制

模板条件控制

ini
# templates/hosts.j2
{% for host in groups['web_servers'] %}
{{ hostvars[host].ansible_default_ipv4.address }} {{ hostvars[host].ansible_hostname }}
{% if not loop.last %}         # 循环控制
{% endif %}
{% endfor %}

文件校验与下载

yaml
- name: 下载安全证书
  get_url:
    url: "https://example.com/certs/{{ cert_name }}"
    dest: "/etc/ssl/certs/{{ cert_name }}"
    checksum: "sha256:{{ cert_sha256 }}"  # 校验文件完整性
    mode: '0600'
    timeout: 30

完整配置案例

yaml
- name: 应用部署配置
  hosts: app_servers
  vars:
    app_name: "myapp"
    app_port: 8080
    features:
      - "logging"
      - "monitoring"
      - "auth"

  tasks:
    - name: 创建应用目录
      file:
        path: "/opt/{{ app_name }}"
        state: directory
        mode: '0755'

    - name: 生成主配置文件
      template:
        src: "templates/app.conf.j2"
        dest: "/opt/{{ app_name }}/app.conf"
        validate: "/usr/sbin/validator -f %s"  # 配置语法校验

    - name: 部署初始化脚本
      copy:
        src: "files/init.sh"
        dest: "/opt/{{ app_name }}/bin/init.sh"
        mode: '0755'

    - name: 配置日志轮转
      template:
        src: "templates/logrotate.j2"
        dest: "/etc/logrotate.d/{{ app_name }}"
        owner: root
        group: root
        mode: '0644'

执行控制

任务执行顺序控制

yaml
- name: 数据库集群部署
  hosts: db_servers
  tasks:
    - name: 预检查磁盘空间  # 必须首先执行
      command: df -h /
      register: disk_check
      tags: always_first

    - name: 安装数据库软件
      yum:
        name: mysql-server
        state: latest
      when: disk_check.rc == 0  # 依赖前序任务结果

    - name: 初始化集群
      command: /usr/bin/mysql-init
      when: 
        - inventory_hostname == groups['db_servers'][0]  # 只在第一个节点执行
        - "'primary' not in group_names"  # 排除特定组

滚动更新控制

yaml
- name: 零停机应用更新
  hosts: app_servers
  serial: 2  # 每次更新2个节点
  max_fail_percentage: 25  # 允许25%节点失败
  strategy: free  # 自由策略(不按顺序)

  tasks:
    - name: 下线节点
      uri:
        url: "http://{{ inventory_hostname }}:8080/health"
        method: DELETE
      delegate_to: load_balancer

    - name: 更新应用
      include_tasks: update_app.yml

    - name: 上线节点
      uri:
        url: "http://{{ inventory_hostname }}:8080/health"
        method: POST
      delegate_to: load_balancer
      async: 60  # 异步操作
      poll: 5    # 每5秒检查

任务执行策略

yaml
- name: 多阶段部署
  hosts: all
  order: sorted  # 按主机名排序执行
  strategy: linear  # 默认线性策略

  tasks:
    - name: 阶段一(并行执行)
      command: /bin/phase1
      async: 120  # 超时120秒
      poll: 0     # 不等待结果(完全异步)

    - name: 阶段二(串行执行)
      command: /bin/phase2
      run_once: true  # 只在一个节点执行
      throttle: 1     # 并发限制为1

错误处理控制

yaml
- name: 关键业务维护
  hosts: production
  any_errors_fatal: true  # 任何错误立即终止play

  tasks:
    - name: 创建维护锁
      file:
        path: /tmp/maintenance.lock
        state: touch

    - block:
        - name: 执行数据迁移
          command: /bin/db-migrate
          register: migrate_result
          until: migrate_result.rc == 0  # 重试直到成功
          retries: 5
          delay: 10

        - name: 验证数据
          script: verify_data.sh
          ignore_errors: yes  # 忽略验证错误

      rescue:
        - name: 紧急回滚
          include_tasks: emergency_rollback.yml
          when: migrate_result.failed  # 仅迁移失败时执行

      always:
        - name: 移除维护锁
          file:
            path: /tmp/maintenance.lock
            state: absent

资源限制控制

yaml
- name: 批量文件处理
  hosts: file_servers
  tasks:
    - name: 压缩日志文件
      command: gzip /var/log/{{ item }}
      loop: "{{ log_files }}"
      throttle: 3  # 并发限制为3
      when: item is match('.*\.log$')  # 仅处理.log文件

    - name: 上传到存储
      synchronize:
        src: /var/log/
        dest: "nfs:/backup/{{ inventory_hostname }}/"
      delegate_to: localhost  # 在控制节点执行
      run_once: true         # 只执行一次

执行流程调试

yaml
- name: 分阶段调试
  hosts: test_servers
  tasks:
    - name: 阶段一检查(标记点)
      meta: noop  # 空操作
      tags: checkpoint1

    - name: 交互式确认
      pause:
        prompt: "请确认阶段一完成,按Enter继续"
        seconds: 30  # 超时自动继续
      when: interactive_mode  # 条件启用

    - name: 显示执行路径
      debug:
        msg: "当前执行路径:{{ play_hosts }}"
      changed_when: false  # 不触发变更状态

    - name: 强制刷新handlers
      meta: flush_handlers  # 立即执行已通知的handlers

最佳实践要点

  1. 执行顺序设计

    yaml
    - name: 关键前置任务
      meta: clear_facts  # 清理facts缓存
      tags: always_run_first
  2. 资源保护机制

    yaml
    - name: 保护性检查
      assert:
        that:
          - ansible_memtotal_mb > 2048
          - ansible_processor_vcpus >= 4
        fail_msg: "系统资源不满足最低要求"
        quiet: yes
  3. 动态执行控制

    yaml
    - name: 环境自适应执行
      include_tasks: "env/{{ deployment_env }}.yml"
      when: deployment_env is defined
  4. 性能优化技巧

    yaml
    - name: 异步批量操作
      command: /bin/long-running-task
      async: 300  # 超时5分钟
      poll: 30    # 每30秒检查
      throttle: "{{ batch_size | default(5) }}"
  5. 执行跟踪记录

    yaml
    - name: 记录执行轨迹
      lineinfile:
        path: /var/log/ansible_trace.log
        line: "{{ ansible_date_time.iso8601 }} {{ ansible_play_name }} {{ task_name }}"
      delegate_to: localhost
      run_once: true

经典案例

部署WEB应用

yaml
---
- name: 部署Web应用
  hosts: demo
  become: yes
  vars:
    app_version: "v1"
    web_root: "/var/www/html"
    app_package_path: "/data/html-{{ app_version }}.tar.gz"
    
  tasks:
    - name: 安装依赖包
      yum:
        name: "{{ item }}"
        state: present
      loop:
        - httpd
        - wget
      ignore_errors: yes  # 简化错误处理,实际应根据需要调整

    - name: 创建网站目录结构
      file:
        path: "{{ web_root }}"
        state: directory
        owner: apache
        group: apache
        mode: '0755'
        recurse: yes

    - name: 验证目录权限
      shell: |
        ls -ld {{ web_root }}
        df -h {{ web_root }}
      register: dir_check
      changed_when: false
      failed_when: "'apache apache' not in dir_check.stdout"

    - name: 显示目录信息
      debug:
        var: dir_check.stdout_lines

    - name: 检查应用包是否存在(控制机)
      local_action:
        module: stat
        path: "{{ app_package_path }}"
      register: app_package

    - name: 显示包路径检查结果
      debug:
        msg: "检查文件路径: {{ app_package_path }},是否存在: {{ app_package.stat.exists }}"

    - name: 部署应用代码
      unarchive:
        src: "{{ app_package_path }}"  # 使用变量
        dest: "{{ web_root }}"
        remote_src: no
        owner: apache
        group: apache
      when: app_package.stat.exists
      notify: restart apache

  handlers:
    - name: restart apache
      service:
        name: httpd
        state: restarted

Roles 是 Ansible 中用于组织 Playbook 的高级功能,它提供了一种标准化的方式来结构化自动化内容,使配置管理更加模块化和可重用。

Ansible Roles 组织

Roles 基本概念

什么是 Role

  • 预定义的文件和目录结构
  • 自包含的自动化单元
  • 可重用的功能组件
  • 支持参数化和默认值

Role 的优势

  • 模块化:将复杂配置分解为独立单元
  • 可重用:同一 Role 可被多个 Playbook 使用
  • 标准化:统一的项目结构
  • 易于维护:逻辑分离,职责明确

Role 标准目录结构

一个完整的 Role 通常包含以下目录结构:

tex
role_name/
├── defaults/          # 默认变量 (最低优先级)
│   └── main.yml
├── vars/              # 角色变量 (较高优先级)
│   └── main.yml
├── tasks/             # 主任务文件
│   └── main.yml
├── handlers/          # 处理器定义
│   └── main.yml
├── templates/         # Jinja2 模板文件
│   └── nginx.conf.j2
├── files/             # 静态文件
│   └── default.html
├── meta/              # 角色依赖关系
│   └── main.yml
└── tests/             # 测试用例
    ├── inventory
    └── test.yml

一键创建目录结构

bash
mkdir -p role_name/{defaults,vars,tasks,handlers,templates,files,meta,tests}
touch role_name/{defaults,vars,tasks,handlers,meta}/main.yaml

Role 各目录详解

defaults/

优先级最低的变量定义,可被其他变量覆盖

  • 必须包含 main.yml
  • 用途:定义角色默认配置(如软件默认版本)
yaml
# defaults/main.yml
nginx_port: 80
app_version: "1.0"

vars/

高优先级变量,通常用于不可覆盖的配置

  • 必须包含 main.yml
  • 用途:定义常量或关键配置
  • 优先级高于 defaults
yaml
# vars/main.yml
security_group: "prod-firewall"
required_dependencies:
  - libssl-dev
  - python3-pip

tasks/

角色核心逻辑,包含任务列表

  • 必须包含 main.yml(入口文件)
  • 支持拆分子任务文件并通过 include_tasks引用
yaml
# tasks/main.yml
- include_tasks: install.yml
- include_tasks: configure.yml when: install_success

- name: 安装 Nginx
  apt:
    name: "{{ package_name }}"
    state: latest

- name: 配置 Nginx
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: restart nginx

handlers/

定义由 notify触发的操作

  • 必须包含 main.yml
  • 典型场景:服务重启/重载配置
yaml
- name: restart nginx
  service:
    name: "{{ service_name }}"
    state: restarted

templates/

Jinja2模板文件存放目录

  • 文件扩展名建议为 .j2
  • 通过 template模块调用
ini
# templates/nginx.conf.j2
server {
    listen {{ nginx_port }};
    server_name {{ server_name }};
}

files/

静态文件存储目录

  • 文件会被直接复制到目标主机
  • 通过 copy模块调用
yaml
# 任务示例
- copy:
    src: files/logo.png
    dest: /var/www/static/logo.png

meta/

角色元数据定义

  • 必须包含 main.yml
  • 用途:声明依赖关系、兼容性等
yaml
# meta/main.yml
dependencies:
  - role: common
    vars: 
      timezone: "Asia/Shanghai"
  - role: nginx
galaxy_info:
  author: "Your Name"
  min_ansible_version: "2.9"

library/ (可选)

存放自定义模块

  • 模块可直接在角色中使用
  • 文件需为 .py格式
python
# library/custom_module.py
from ansible.module_utils.basic import *
def main():
    # 模块逻辑

module_utils/(可选)

存放自定义模块依赖的公共库

  • library/中的模块引用
  • 需Python标准格式

tests/ (可选)

角色测试目录

  • 包含测试Playbook和Inventory
yaml
# tests/test.yml
- hosts: localhost
  roles:
    - role: ../  # 测试当前角色

Role 的使用方式

在 Playbook 中调用 Role

yaml
- hosts: webservers
  roles:
    - common
    - role: nginx
      vars:
        http_port: 8080
    - { role: mysql, when: "db_required" }

条件执行 Role

yaml
roles:
  - role: nginx
    when: ansible_os_family == "Debian"

动态导入 Role

yaml
tasks:
  - name: 包含数据库角色
    include_role:
      name: "{{ db_type }}"

Role 最佳实践

  1. 命名规范

    • 使用小写字母和下划线
    • 如:nginx, postgresql, web_app
  2. 变量管理

    • 默认值放在 defaults/main.yml
    • 强制变量放在 vars/main.yml
    • 敏感变量使用 Ansible Vault 加密
  3. 任务组织

    • 复杂 Role 可将任务拆分到多个文件:
      tasks/
      ├── main.yml
      ├── install.yml
      └── configure.yml
    • 在 main.yml 中引入:
      yaml
      - include_tasks: install.yml
      - include_tasks: configure.yml
  4. 依赖管理

    • 明确声明依赖关系
    • 避免循环依赖
  5. 文档说明

    • 每个 Role 包含 README.md
    • 说明变量、用法和示例

经典案例

部署Nginx

目录结构

bash
roles/nginx
├── defaults
│   └── main.yaml
├── files
│   ├── install_nginx.sh
│   └── nginx-1.26.3.tar.gz
├── handlers
│   └── main.yaml
├── meta
│   └── main.yaml
├── tasks
│   └── main.yaml
├── templates
│   └── nginx.conf.j2
├── tests
└── vars
    └── main.yaml

模板文件初始化

bash
mkdir -p nginx/{defaults,vars,tasks,handlers,templates,files,meta,tests}
touch nginx/{defaults,vars,tasks,handlers,meta}/main.yaml

入口文件

nginx.yaml

yaml
---
- name: 部署Nginx
  hosts: nginx
  gather_facts: True 			# 开启系统内置变量
  roles: 						# 启用roles原型配置
    - role: nginx 					# 执行nginx原型模组
      vars:
        NGINX_PORT: 8080

定义变量

默认变量

nginx/defaults/main.yaml

yaml
---
# 定义域名
SERVER_NAME: localhost

# 版本控制
NGINX_VERSION: 1.26.3

# 安装路径
NGINX_PREFIX: /usr/local/nginx

# Nginx 程序用户
NGINX_USER: www

# 解压路径
TAR_DIR: /usr/src/
自定义变量

nginx/vars/main.yaml

yaml
---
NGINX_CONFIGURE: >-
    --prefix={{ NGINX_PREFIX }} 
    --with-http_ssl_module 
    --with-http_stub_status_module 
    --with-http_realip_module 
    --with-http_gzip_static_module 
    --with-http_v2_module 
    --with-pcre 
    --with-stream 
    --with-stream_ssl_module 
    --with-threads 
    --with-file-aio 
    --user={{ NGINX_USER }} 
    --group={{ NGINX_USER }}

tasks 任务编排

nginx/tasks/main.yaml

yaml
---
- name: 创建用户
  user:
    name: "{{ NGINX_USER }}"
    state: present
    shell: /sbin/nologin
- name: 复制安装包并解压
  unarchive:
    src: "nginx-{{ NGINX_VERSION }}.tar.gz"
    dest: "{{ TAR_DIR }}"
    owner: "{{ NGINX_USER }}"
    group: "{{ NGINX_USER }}"
- name: 安装依赖
  yum: 
    name: "{{ item }}"
    state: present
  loop:
    - gcc
    - make
    - zlib
    - openssl-devel
    - zlib-devel
    - pcre-devel
    - pcre
- name: 复制 install_nginx.sh
  template: 
    src: install_nginx.sh.j2
    dest: "/usr/src/nginx-{{ NGINX_VERSION }}/install_nginx.sh"
    owner: "{{ NGINX_USER }}"
    group: "{{ NGINX_USER }}"
    mode: "644"
- name: 安装脚本
  shell: 
    cmd: ./install_nginx.sh
    chdir: "/usr/src/nginx-{{ NGINX_VERSION }}"
  register: install_result
- name: 查看脚本执行结果
  debug:
    var: install_result
- name: 复制nginx.conf
  template: 
    src: nginx.conf.j2 
    dest: "{{ NGINX_PREFIX }}/nginx/conf/nginx.conf"
    owner: "{{ NGINX_USER }}"                   	# 文件属主
    group: "{{ NGINX_USER }}"                  		# 文件属组
    mode: "0644"                  					# 文件权限
    backup: yes                   					# 修改前创建备份
  notify: start_nginx

handlers 配置

nginx/handlers/main.yaml

yaml
---
- name: start_nginx 								# 启动Nginx
  shell: "{{ NGINX_PREFIX }}/sbin/nginx"
  notify: check_nginx
- name: stop_nginx 									# 停止Nginx
  shell: "{{ NGINX_PREFIX }}/sbin/nginx -s stop"
- name: reload_nginx 								# 重启Nginx
  shell: "{{ NGINX_PREFIX }}/sbin/nginx -s reload"
- name: check_nginx									# 查看启动结果
  shell:
    cmd: "ss -antup | grep {{ NGINX_PORT }}"

Nginx 配置文件

nginx/templates/nginx.conf.j2

ini
user                 {{ NGINX_USER }};
worker_processes     auto;
worker_rlimit_nofile 65535;

events {
    multi_accept       on;
    worker_connections 65535;
}

http {
    charset                utf-8;
    sendfile               on;
    tcp_nopush             on;
    tcp_nodelay            on;
    server_tokens          off;
    log_not_found          off;
    types_hash_max_size    2048;
    types_hash_bucket_size 64;
    client_max_body_size   16M;

    # MIME
    include                mime.types;
    default_type           application/octet-stream;
    server {
        listen      {{ NGINX_PORT }};
        server_name {{ SERVER_NAME }};
        root        html;

        # index.html fallback
        location / {
            index index.html
            try_files $uri $uri/ /index.html;
        }
    }
}

安装程序脚本

nginx/templates/install_nginx.sh.j2

shell
#!/bin/bash

./configure {{ NGINX_CONFIGURE }}

[ $? -eq 0 ] && echo "预配置成功" || echo " 预配置失败"

make -j$(nproc) && make install
[ $? -eq 0 ] && echo "编译成功" || echo " 编译失败"

语法检测

bash
ansible-playbook -C nginx.yaml

执行自动化任务

bash
ansible-playbook nginx.yaml