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 实现自动化部署流水线
相关推荐
b***25112 小时前
储能电池包的自动化产线探秘|深圳比斯特自动化
运维·自动化
ZeroNews内网穿透2 小时前
新版发布!“零讯”微信小程序版本更新
运维·服务器·网络·python·安全·微信小程序·小程序
工控小楠2 小时前
涡街流量计温度数据的协议桥梁:Modbus RTU 转 Profinet 网关的自动化应用
运维·自动化
<但凡.2 小时前
Linux 修炼:进程控制(一)
linux·运维·服务器·bash
m0_464608263 小时前
Ansible实现自动化运维
运维·自动化·ansible
✎﹏赤子·墨筱晗♪3 小时前
Ansible Playbook 入门指南:从基础到实战
linux·服务器·ansible
小白不想白a3 小时前
【Ansible】使用ansible部署k8s集群前的准备工作脚本
容器·kubernetes·ansible
m0_464608264 小时前
Ansible Playbook:自动化配置管理的利器
运维·自动化·ansible
DDC楼宇自控与IBMS集成系统解读4 小时前
DDC 楼宇自控系统 + 3D 可视化运维管理平台融合解决方案
运维·3d