Ansible 生产级自动化指南:Playbook、Handlers、Jinja2 全解析

一、什么是 Ansible 剧本(Playbook)?

Ansible 剧本(Playbook)是使用 YAML 格式 编写的自动化脚本文件,用于定义一系列任务(Tasks),实现对多台主机的批量配置、部署、维护

与临时命令(Ad-hoc)相比,剧本具有可重复执行、易于维护、支持复杂流程等优势,是企业级自动化的核心工具。

剧本 vs Ad-hoc 命令

特性 Playbook(剧本) Ad-hoc 命令(临时命令)
可重复性 高(文件化,可版本控制) 低(命令行输入,难追溯)
复杂任务支持 支持多任务、条件判断、循环等 仅支持单条命令
变量支持 支持多种变量定义方式 不支持或需命令行传参
典型应用场景 服务部署、配置管理、CI/CD 流水线 临时测试、快速排查、单次操作

💡 一句话总结
Ad-hoc 是"即兴发挥",Playbook 是"编排好的交响乐"


二、剧本基础:YAML 语法与执行流程

1. 剧本基本结构

yaml 复制代码
- name: 配置 Web 服务器集群
  hosts: webservers
  become: yes
  gather_facts: yes
  tasks:
    - name: 安装 Nginx
      apt:
        name: nginx
        state: present

    - name: 启动并启用 Nginx 服务
      systemd:
        name: nginx
        state: started
        enabled: yes

2. YAML 书写规范(必须遵守)

  • 使用 空格缩进 (推荐 2 空格),禁止使用 Tab
  • 冒号后加空格:name: Nginx
  • 列表项以 - 开头
  • 使用 --- 开头(YAML 文档分隔符)

⚠️ 常见错误示例

yaml 复制代码
tasks:
- name: 安装软件包
  yum: name=httpd state=present  # ❌ 错误:未换行,模块参数未对齐

正确写法:

yaml 复制代码
tasks:
  - name: 安装 Apache
    yum:
      name: httpd
      state: present

三、实战案例

案例 1:: 创建目录并分发文件

1.创建目录/server/files/

2.把/etc/hosts文件发送过去/data/files/

  • 中间转换步骤: 任务的步骤-->模块(命令行)
sh 复制代码
1. 创建目录/server/files/
ansible all  -m file -a 'path=/data/files/state=directory'

2. 分发
ansible all -m copy -a 'src=/etc/hosts dest=/data/files/ '
  • 书写剧本
sh 复制代码
[root@m01 /server/playbooks]# cat dir_copy.yaml 
---
- hosts: all
  tasks:
    - name: 01.mkdir dir
      file:
        path: /data/files
        state: directory
    - name: 02.copy file
      copy:
        src: /etc/hosts
        dest: /server/files/      

案例 2:部署 Nginx + 静态网站(多任务编排)

目标 :在 webservers 组部署 Nginx,并发布一个简单的前端页面。

yaml 复制代码
# playbooks/deploy_nginx.yml
---
- name: 部署 Nginx 并发布网站
  hosts: webservers
  become: yes
  vars:
    web_root: /var/www/html
    site_title: "Welcome to My Site"

  tasks:
    - name: 确保 Nginx 已安装
      package:
        name: nginx
        state: present

    - name: 创建网站根目录
      file:
        path: "{{ web_root }}"
        state: directory
        mode: '0755'

    - name: 部署 index.html 页面
      template:
        src: templates/index.html.j2
        dest: "{{ web_root }}/index.html"
        mode: '0644'

    - name: 启动并启用 Nginx 服务
      systemd:
        name: nginx
        state: started
        enabled: yes

    - name: 确保防火墙放行 HTTP
      firewalld:
        service: http
        permanent: yes
        state: enabled
      when: ansible_os_family == "RedHat"

🔍 说明

  • 使用 template 模块动态生成 HTML 页面(支持变量注入)
  • when 条件判断:仅在 RHEL/CentOS 上配置防火墙
  • vars 定义路径和标题,便于后期修改

案例 3:使用变量文件管理配置(vars_files

场景:多个剧本共用数据库连接信息。

yaml 复制代码
# group_vars/all/db_vars.yml
db_host: "db.internal.prod"
db_port: 5432
db_user: "app_user"
db_password: "secure_password_123"
yaml 复制代码
# playbooks/config_app.yml
---
- name: 配置应用服务
  hosts: app_servers
  become: yes
  vars_files:
    - group_vars/all/db_vars.yml  # 引入变量文件

  tasks:
    - name: 创建应用配置文件
      template:
        src: templates/app.conf.j2
        dest: /opt/app/config/app.conf
jinja2 复制代码
# templates/app.conf.j2
[database]
host = {{ db_host }}
port = {{ db_port }}
user = {{ db_user }}
password = {{ db_password }}

优势:配置集中管理,避免硬编码。

案例 4:基于主机组的变量管理(group_vars

场景:不同环境(dev/staging/prod)使用不同配置。

复制代码
group_vars/
  dev/
    vars.yml          # dev 环境变量
  staging/
    vars.yml          # staging 环境变量
  prod/
    vars.yml          # prod 环境变量
  all/
    common.yml        # 所有环境共用变量(如监控 agent 配置)
yaml 复制代码
# group_vars/prod/vars.yml
app_env: "production"
log_level: "error"
backup_schedule: "0 2 * * *"
yaml 复制代码
# playbooks/deploy_app.yml
---
- name: 部署应用(根据环境自动适配)
  hosts: all
  become: yes
  tasks:
    - name: 输出当前环境
      debug:
        msg: "当前部署环境:{{ app_env }}"

最佳实践

  • group_vars/all/ 存放通用配置
  • group_vars/<env>/ 存放环境特有配置
  • 结合 ansible-playbook -i inventory_prod ... 实现多环境部署

案例 5:使用 Facts 变量获取主机信息

场景:生成系统健康报告。

yaml 复制代码
# playbooks/system_report.yml
---
- name: 收集系统信息
  hosts: all
  gather_facts: yes  # 默认开启

  tasks:
    - name: 输出主机信息
      debug:
        msg: |
          主机名: {{ ansible_hostname }}
          IP 地址: {{ ansible_default_ipv4.address }}
          操作系统: {{ ansible_distribution }} {{ ansible_distribution_version }}
          CPU 核心数: {{ ansible_processor_vcpus }}
          内存总量: {{ ansible_memtotal_mb }} MB
          磁盘空间: {{ ansible_mounts | selectattr('mount', '==', '/') | map(attribute='size_total') | first | int / 1024 / 1024 / 1024 | round(2) }} GB

🔍 Facts 常用变量

  • {``{ ansible_hostname }}:主机名
  • {``{ ansible_default_ipv4.address }}:主 IP
  • {``{ ansible_distribution }}:系统类型
  • {``{ ansible_memtotal_mb }}:内存(MB)
  • {``{ ansible_processor_vcpus }}:CPU 核心数
    ⚠️ 性能提示 :若无需主机信息,可设置 gather_facts: no 加速执行。

案例 6:Register 变量 ------ 捕获命令输出

场景:检查服务状态并根据结果执行操作。

yaml 复制代码
# playbooks/check_service.yml
---
- name: 检查 Nginx 运行状态
  hosts: webservers
  become: yes

  tasks:
    - name: 获取 Nginx 进程信息
      shell: ps aux | grep nginx | grep -v grep
      register: nginx_status
      ignore_errors: yes  # 即使无进程也不报错

    - name: 输出检查结果
      debug:
        msg: "Nginx 正在运行,PID: {{ nginx_status.stdout }}"
      when: nginx_status.rc == 0

    - name: 重启 Nginx(如果未运行)
      systemd:
        name: nginx
        state: restarted
      when: nginx_status.rc != 0

Register 关键字段

  • stdout:标准输出
  • stderr:错误输出
  • rc:返回码(0 表示成功)
  • changed:是否改变了系统状态

四、流程控制:循环、判断与触发器

1. 循环(Loop)

应用场景:批量创建用户、目录、服务管理等。

基本循环(单变量)
yaml 复制代码
- hosts: web01
  tasks:
    - name: 重启多个服务
      systemd:
        name: "{{ item }}"
        state: restarted
      loop:
        - nginx
        - mysql
        - redis
多变量循环(字典列表)
yaml 复制代码
- hosts: web01
  tasks:
    - name: 批量创建用户
      user:
        name: "{{ item.name }}"
        uid: "{{ item.uid }}"
        groups: "{{ item.groups }}"
        state: present
      loop:
        - { name: 'app_user', uid: '2001', groups: 'www-data' }
        - { name: 'db_user', uid: '2002', groups: 'mysql' }
        - { name: 'backup_user', uid: '2003', groups: 'backup' }
文件循环示例
yaml 复制代码
- hosts: app_servers
  tasks:
    - name: 创建多个日志目录
      file:
        path: "/var/log/{{ item }}"
        state: directory
        owner: root
        group: root
        mode: '0755'
      loop:
        - nginx
        - mysql
        - redis
        - application

建议 :优先使用 loopwith_items 已逐步被取代。


2. Handlers 触发器

应用场景:配置文件变更后才重启服务,避免无谓重启。

使用 Handlers 的正确方式
yaml 复制代码
- hosts: web_servers
  tasks:
    - name: 配置 Nginx
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        backup: yes
      notify: 重新加载 Nginx

    - name: 配置 SSL 证书
      copy:
        src: files/ssl_cert.pem
        dest: /etc/nginx/ssl/cert.pem
      notify: 重新加载 Nginx

  handlers:
    - name: 重新加载 Nginx
      systemd:
        name: nginx
        state: reloaded
数据库配置变更示例
yaml 复制代码
- hosts: db_servers
  tasks:
    - name: 更新 MySQL 配置
      template:
        src: templates/my.cnf.j2
        dest: /etc/mysql/my.cnf
      notify: 重启 MySQL 服务

    - name: 调整内存参数
      lineinfile:
        path: /etc/mysql/my.cnf
        line: "innodb_buffer_pool_size = {{ db_memory }}G"
        regexp: "^innodb_buffer_pool_size"
      notify: 重启 MySQL 服务

  handlers:
    - name: 重启 MySQL 服务
      systemd:
        name: mysql
        state: restarted
多 Handler 触发示例
yaml 复制代码
- hosts: monitoring_servers
  tasks:
    - name: 配置 Prometheus
      template:
        src: templates/prometheus.yml.j2
        dest: /etc/prometheus/prometheus.yml
      notify:
        - 重新加载 Prometheus
        - 重启 Alertmanager

    - name: 更新告警规则
      copy:
        src: files/alerts.rules
        dest: /etc/prometheus/alerts.rules
      notify: 重新加载 Prometheus

  handlers:
    - name: 重新加载 Prometheus
      systemd:
        name: prometheus
        state: reloaded

    - name: 重启 Alertmanager
      systemd:
        name: alertmanager
        state: restarted

Handlers 最佳实践

  • 有意义的命名:handler 名称应清晰描述其作用
  • 单一职责:每个 handler 只负责一个服务的操作
  • 条件触发:只有相关配置真正变更时才执行
  • 执行顺序:handlers 在 play 末尾按定义顺序执行
高级 Handler 用法
yaml 复制代码
- hosts: all
  tasks:
    - name: 配置系统参数
      template:
        src: templates/sysctl.conf.j2
        dest: /etc/sysctl.conf
      notify: 应用内核参数

    - name: 配置 limits.conf
      template:
        src: templates/limits.conf.j2
        dest: /etc/security/limits.conf
      notify: 重新登录生效提示

  handlers:
    - name: 应用内核参数
      command: sysctl -p
      listen: "系统配置变更"

    - name: 重新登录生效提示
      debug:
        msg: "limits.conf 已更新,需要重新登录会话才能生效"
      listen: "系统配置变更"

# 使用 listen 分组多个 handlers
- name: 重启所有服务
  debug:
    msg: "执行系统重启前检查"
  listen: "系统维护"

🔍 关键点

  • notify 触发 handler 名称
  • handlers 块必须放在 tasks 之后
  • 只有文件实际变更时才会触发
  • 使用 listen 可以实现 handler 分组

3. 条件判断(When)

应用场景:根据系统类型、主机名、变量等条件执行任务。

基于系统类型安装软件
yaml 复制代码
- hosts: all
  tasks:
    - name: CentOS/RHEL 安装软件包
      yum:
        name: "{{ item }}"
        state: present
      loop:
        - epel-release
        - htop
        - iotop
      when: ansible_os_family == "RedHat"

    - name: Ubuntu/Debian 安装软件包
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - htop
        - iotop
        - net-tools
      when: ansible_os_family == "Debian"
使用正则匹配(match)
yaml 复制代码
    - name: 生产环境特殊配置
      debug:
        msg: "应用生产环境安全配置"
      when: ansible_hostname is match("prod.*")

    - name: 开发环境宽松配置
      debug:
        msg: "应用开发环境调试配置"
      when: ansible_hostname is match("dev.*")
复合条件与逻辑运算
yaml 复制代码
    - name: 高内存服务器优化配置
      template:
        src: templates/high_memory.j2
        dest: /etc/systemd/system/custom.service
      when: 
        - ansible_memtotal_mb > 8192
        - ansible_processor_vcpus >= 4
        - deployment_env == "production"

    - name: 数据库服务器专用配置
      template:
        src: templates/db_optimization.j2
        dest: /etc/security/limits.d/db.conf
      when: 
        - "'db' in group_names"
        - ansible_architecture == "x86_64"

    - name: 紧急维护模式
      debug:
        msg: "进入维护模式,暂停非关键服务"
      when: maintenance_mode | bool and emergency_maintenance | bool
文件存在性检查
yaml 复制代码
    - name: 检查自定义配置是否存在
      stat:
        path: /etc/application/custom.conf
      register: custom_config

    - name: 应用自定义配置
      template:
        src: templates/application.conf.j2
        dest: /etc/application/application.conf
      when: custom_config.stat.exists

    - name: 使用默认配置
      copy:
        src: files/default.conf
        dest: /etc/application/application.conf
      when: not custom_config.stat.exists
版本比较条件
yaml 复制代码
    - name: 检查内核版本
      shell: uname -r
      register: kernel_version

    - name: 安装新版驱动
      yum:
        name: kmod-special-driver
        state: latest
      when: kernel_version.stdout is version('5.0', '>=')

    - name: 安装旧版驱动
      yum:
        name: kmod-special-driver-legacy
        state: present
      when: kernel_version.stdout is version('5.0', '<')

常用判断符号

  • ==, != - 等于/不等于

  • is match("正则"), is not match("正则") - 正则匹配

  • >, >=, <, <= - 数值比较

  • and, or - 逻辑组合

  • in, not in - 包含判断

  • is version('1.0', '>=') - 版本比较
    ⚠️ 注意事项

  • 使用 | bool 过滤器确保布尔值比较正确

  • 复杂的条件判断建议拆分为多个条件或使用变量预处理

  • 条件判断应保持可读性,避免过度复杂

五、Jinja2 模板引擎:动态配置文件

1. 基本使用(变量注入)

jinja2 复制代码
# templates/motd.j2
#######################################
欢迎访问 {{ ansible_hostname }}
IP 地址: {{ ansible_default_ipv4.address }}
内存: {{ ansible_memtotal_mb }} MB
CPU: {{ ansible_processor_vcpus }} 核
操作系统: {{ ansible_distribution }} {{ ansible_distribution_version }}
yaml 复制代码
- name: 分发 MOTD 文件
  template:
    src: templates/motd.j2
    dest: /etc/motd

2. 模板中的判断(if/else)

jinja2 复制代码
# templates/keepalived.conf.j2
{% if ansible_hostname == "lb01" %}
state MASTER
priority 100
{% elif ansible_hostname == "lb02" %}
state BACKUP
priority 90
{% endif %}

3. 模板中的循环(for)

jinja2 复制代码
# 生成 IP 列表
{% for i in range(5, 11) %}
server 10.0.0.{{ i }}:8080;
{% endfor %}

# 遍历列表
{% for name in ['oldboy', 'lidao'] %}
user {{ name }};
{% endfor %}

最佳实践

  • 所有需变量替换的文本文件使用 .j2 后缀
  • 使用 template 模块分发
  • 二进制或静态文件使用 copy 模块

六、高级技巧与调试指南

1. 调试与语法检查

命令 作用
ansible-playbook --syntax-check playbook.yml 仅检查语法
ansible-playbook -C playbook.yml 模拟运行(不实际修改)
ansible-playbook --step playbook.yml 单步执行,交互式确认

2. 使用 Tags 进行任务分类

yaml 复制代码
- name: 安装 Nginx
  yum:
    name: nginx
    state: present
  tags: install

- name: 配置 Nginx
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  tags: config

- name: 重启 Nginx
  systemd:
    name: nginx
    state: restarted
  tags: restart
bash 复制代码
# 只运行安装任务
ansible-playbook -t install site.yml

# 跳过重启任务
ansible-playbook --skip-tags restart site.yml

# 查看所有标签
ansible-playbook --list-tags site.yml

3. 忽略非致命错误

yaml 复制代码
- name: 创建可能已存在的目录
  file:
    path: /data/logs
    state: directory
  ignore_errors: yes

适用场景:用户已存在、目录已创建等幂等性报错。

七、变量管理最佳实践总结

变量定义方式 适用场景 推荐指数
vars: 在剧本中定义 单个 Play 内部使用,临时变量 ⭐⭐⭐⭐
vars_files: 引入文件 多个 Play 共享同一组变量 ⭐⭐⭐⭐
group_vars/ 分组变量 多环境、大型项目、团队协作(强烈推荐) ⭐⭐⭐⭐⭐
Facts 变量 获取主机信息(IP、OS、硬件等) ⭐⭐⭐⭐
register 注册变量 捕获命令输出,用于条件判断 ⭐⭐⭐⭐

八、总结:Ansible 自动化核心原则

  1. 结构化 :使用 group_varshost_vars 实现配置分离
  2. 可维护 :为每个任务添加 nametags
  3. 幂等性:确保剧本可重复执行而不产生副作用
  4. 安全性:敏感信息使用 Ansible Vault 加密
  5. 测试先行 :使用 --check--diff 模拟变更

最终建议

  • 剧本即代码,纳入版本控制(Git)
  • 使用 pre-commit 钩子自动检查 YAML 语法
  • 结合 CI/CD 实现自动化部署流水线
相关推荐
SelectDB1 天前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
XIAOHEZIcode2 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220703 天前
如何搭建本地yum源(上)
运维
大树886 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠6 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质6 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
Inhand陈工6 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智6 天前
ARP代理--工作原理
运维·网络·arp·arp代理
shushangyun_6 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
施努卡机器视觉6 天前
SNK施努卡侧滑门锁上滑轮总成自动化装配线,从零件到组件,全流程精密制造方案
运维·自动化·制造