Ansible 企业级实战:Playbook 与 Roles 完全指南

前言

在当今 DevOps 浪潮中,Ansible 凭借其无代理架构YAML 语法 以及海量模块 ,成为配置管理、应用部署、云资源编排的首选工具。而 PlaybookRoles 则是 Ansible 实现"基础设施即代码"的核心组件。

本文将带你从零开始,深入掌握 Playbook 的编写技巧、Roles 的设计思想,并补充大量生产环境中经常用到的高阶知识点,包括:

  • 变量优先级(11个来源的覆盖规则)

  • 条件判断与循环

  • 错误处理(ignore_errors、block、rescue)

  • 调试技巧(debug 模块、register 变量)

  • 标签的高级用法

  • 角色依赖与 ansible-galaxy

  • 性能优化(SSH pipelining、facts 缓存)

无论你是刚入门的运维新人,还是希望提升自动化脚本质量的老手,都能从本文获益。


一、Playbook 简介

1.1 什么是 Playbook?

Playbook 是 Ansible 的配置、部署、编排语言,以 YAML 格式定义一系列任务(tasks),让远程主机达到预期状态。你可以把它理解为:

  • 剧本 -- 定义"谁(hosts)"、"以什么身份(remote_user)"、"做什么(tasks)";

  • 任务清单 -- 被控节点必须按顺序完成的所有事项。

1.2 什么时候用 Playbook?

  • 简单操作 → 使用 ansible ad-hoc 命令(如 ansible all -m ping)。

  • 复杂流程 → 必须使用 Playbook。例如:部署一个 Web 应用需要安装包、改配置、启动服务、注册监控...... 这时 Playbook 就是最佳选择。


二、Playbook 的核心组成

一个 Playbook 可以包含多个 play,每个 play 由以下元素构成:

组件 说明
Tasks 任务列表,按顺序执行,每个任务调用一个模块。
Variables 变量,使 Playbook 可复用。
Templates 模板文件(Jinja2),动态生成配置文件。
Handlers 处理器,仅在任务通知(notify)时触发,通常用于重启服务。
Roles 角色,将 tasks、vars、handlers、templates 等按规范目录组织。

下面是一个最简单的 Playbook 示例(创建用户并设置密码):

yaml

复制代码
# a.yml
- hosts: web
  remote_user: root
  tasks:
    - name: create user zhangsan
      user:
        name: zhangsan
        password: "{{ 'aptech' | password_hash('sha512') }}"
        state: present
      tags:
        - ccc

2.1 常用执行命令

命令 作用
ansible-playbook --syntax-check a.yml 语法检查
ansible-playbook -C a.yml 预测试(dry run)
ansible-playbook --list-hosts a.yml 列出受控主机
ansible-playbook --list-tasks a.yml 列出所有任务
ansible-playbook --list-tags a.yml 列出所有标签
ansible-playbook a.yml 正式执行
ansible-playbook a.yml --tags "ccc" 只运行指定标签任务
ansible-playbook a.yml --skip-tags "ccc" 跳过指定标签

三、Hosts 与 Users 详解

3.1 基本定义

yaml

复制代码
- hosts: web          # 主机组(来自 inventory)
  remote_user: zhangsan   # 以 zhangsan 身份执行

3.2 使用 sudo 提权

yaml

复制代码
- hosts: web
  remote_user: zhangsan
  become: yes
  become_method: sudo
  become_user: root    # 切换到 root 执行

注意 :在 /etc/ansible/hosts 中也可以定义连接用户和密码:

ini

复制代码
[web]
192.168.207.138 ansible_ssh_user=root ansible_ssh_pass=123

3.3 完整 Playbook 示例(测试连通性)

yaml

复制代码
# b.yaml
- hosts: web
  remote_user: zhangsan
  tasks:
    - name: test connection
      ping:

四、任务列表与 Action

Tasks 是 Playbook 的核心,每个任务必须包含一个模块调用(称为 action)。格式有两种:

yaml

复制代码
# 老式写法
- action: yum name=httpd state=latest

# 推荐写法(更清晰)
- name: install httpd
  yum:
    name: httpd
    state: latest

多主机多任务示例(同一 Playbook 中包含两个 play):

yaml

复制代码
# a.yml (多play)
- hosts: web
  remote_user: root
  tasks:
    - name: create user
      user:
        name: zhangsan
        password: "{{ 'aptech' | password_hash('sha512') }}"
        state: present
      tags: ccc

- hosts: db
  remote_user: root
  tasks:
    - name: copy file to db
      copy:
        src: /etc/passwd
        dest: /opt
      tags: ddd

五、Handlers -- 触发式操作

Handlers 只有在被 notify 时才会在 play 的末尾 执行一次(即使多次 notify 也只执行一次)。适用于配置文件改动后重启服务的场景。

5.1 基础示例:安装 Apache 并启动

yaml

复制代码
# a.yml (无 handlers)
- hosts: web
  remote_user: root
  tasks:
    - name: install httpd
      yum:
        name: httpd
        state: latest
    - name: copy config file
      copy:
        src: /root/conf/httpd.conf
        dest: /etc/httpd/conf/httpd.conf
    - name: start httpd
      service:
        name: httpd
        enabled: true
        state: started

5.2 使用 Handlers:当配置文件变化时重启

yaml

复制代码
# b.yml
- hosts: web
  remote_user: root
  tasks:
    - name: change port in config
      copy:
        src: /root/conf/httpd.conf
        dest: /etc/httpd/conf/httpd.conf
      notify:
        - restart httpd server
  handlers:
    - name: restart httpd server
      service:
        name: httpd
        state: restarted

注意 :被控节点需关闭 SELinux,否则 copy 模块可能因上下文报错。

临时关闭:setenforce 0;永久关闭:修改 /etc/selinux/config


六、Templates -- 动态配置管理

Ansible 使用 Jinja2 模板引擎,可以在配置文件中嵌入变量,实现"一套模板,多环境配置"。

6.1 创建模板文件

jinja2

复制代码
# templates/httpd.conf.j2
Listen {{ http_port }}
ServerName {{ ansible_fqdn }}

6.2 在 inventory 中定义主机变量

ini

复制代码
# /etc/ansible/hosts
[web]
192.168.207.138 ansible_ssh_user=root ansible_ssh_pass=123 http_port=8888

[db]
192.168.207.139 ansible_ssh_user=root ansible_ssh_pass=123

6.3 编写 Playbook 使用模板

yaml

复制代码
# apache.yml
- hosts: web
  remote_user: root
  vars:
    - package: httpd
    - service: httpd
  tasks:
    - name: install httpd
      yum:
        name: "{{ package }}"
        state: latest
    - name: deploy config from template
      template:
        src: /root/templates/httpd.conf.j2
        dest: /etc/httpd/conf/httpd.conf
      notify:
        - restart httpd
    - name: start and enable httpd
      service:
        name: "{{ service }}"
        enabled: yes
        state: started
  handlers:
    - name: restart httpd
      service:
        name: "{{ service }}"
        state: restarted

6.4 验证结果

在被控节点执行:

bash

复制代码
grep -i listen /etc/httpd/conf/httpd.conf | grep -v "#"
# 应输出 Listen 8888
grep -i servername /etc/httpd/conf/httpd.conf | grep -v "#"
# 应输出 ServerName node2.example.com (取决于实际主机名)

七、Roles -- 结构化自动化

当 Playbook 变得庞大(超过 200 行),Roles 是必然选择。Roles 将 变量、任务、 handlers、模板、静态文件 按固定目录组织,实现复用分享

7.1 Role 的目录结构

bash

复制代码
ansible-galaxy init mysql

生成如下结构:

text

复制代码
mysql/
├── defaults/      # 默认变量(优先级最低)
│   └── main.yml
├── files/         # 静态文件(直接复制)
├── handlers/      # 处理器
│   └── main.yml
├── meta/          # 依赖关系、作者信息
│   └── main.yml
├── tasks/         # 主要任务(核心)
│   └── main.yml
├── templates/     # Jinja2 模板
├── tests/         # 测试
└── vars/          # 变量(优先级高于 defaults)
    └── main.yml

7.2 完整案例:部署 MySQL 5.7

步骤1:生成角色

bash

复制代码
ansible-galaxy init mysql
cd mysql
步骤2:编写 tasks/main.yml

yaml

复制代码
# mysql/tasks/main.yml
- name: Install MySQL server
  yum:
    name: mysql-server
    state: present

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

- name: Start and enable MySQL
  service:
    name: mysqld
    state: started
    enabled: yes
步骤3:编写 handlers/main.yml

yaml

复制代码
# mysql/handlers/main.yml
- name: Restart MySQL
  service:
    name: mysqld
    state: restarted
步骤4:定义变量(defaults/main.yml)

yaml

复制代码
# mysql/defaults/main.yml
mysql_port: 3306
mysql_user: root
mysql_datadir: /var/lib/mysql
步骤5:创建模板 templates/my.cnf.j2

jinja2

复制代码
[mysqld]
port = {{ mysql_port }}
user = {{ mysql_user }}
datadir = {{ mysql_datadir }}
步骤6:在 Playbook 中使用 Role

yaml

复制代码
# deploy_mysql.yml
- hosts: db
  roles:
    - mysql
步骤7:执行

bash

复制代码
ansible-playbook deploy_mysql.yml

八、补充知识点(生产环境必备)

以下内容是原文未详细展开,但在日常运维中极其重要的进阶知识。

8.1 变量优先级(11个来源)

Ansible 变量有严格的优先级,越靠后优先级越高(后面的覆盖前面的):

  1. 命令行 -e 变量(最高)

  2. Role 的 vars 目录

  3. Role 的 defaults 目录

  4. Playbook 中的 vars

  5. Inventory 中的 host_vars / group_vars

  6. Inventory 中的 vars

  7. Facts 变量(ansible_*

  8. 注册变量(register

  9. 角色参数

  10. 内置变量

  11. 默认配置(最低)

最佳实践

  • 通用默认值放 defaults/main.yml

  • 环境差异值放 group_vars/

  • 敏感或临时值用 -e 传入

8.2 条件判断(when)

yaml

复制代码
- name: Install only on RedHat family
  yum:
    name: nginx
  when: ansible_os_family == "RedHat"

- name: Shut down Debian systems
  command: /sbin/shutdown -t now
  when: ansible_os_family == "Debian"

支持逻辑运算符:andor、括号分组,以及判断变量是否存在。

8.3 循环(loop / with_*)

推荐使用 loop(标准格式):

yaml

复制代码
- name: Install multiple packages
  yum:
    name: "{{ item }}"
    state: present
  loop:
    - httpd
    - git
    - vim

- name: Create users with loop over dict
  user:
    name: "{{ item.name }}"
    uid: "{{ item.uid }}"
  loop:
    - { name: 'alice', uid: 1001 }
    - { name: 'bob', uid: 1002 }

8.4 错误处理

忽略错误(ignore_errors)

yaml

复制代码
- name: This may fail
  command: /bin/false
  ignore_errors: yes
强制失败(failed_when)

yaml

复制代码
- name: Check if file contains ERROR
  command: grep ERROR /var/log/app.log
  register: result
  failed_when: result.rc == 0 and "FATAL" in result.stdout
block + rescue + always(类似 try-catch-finally)

yaml

复制代码
- block:
    - name: Attempt risky operation
      command: /usr/bin/might_fail
  rescue:
    - name: Run when block fails
      debug:
        msg: "Block failed, executing rescue"
  always:
    - name: Always run
      command: /usr/bin/cleanup

8.5 调试技巧(debug 模块 + register)

yaml

复制代码
- name: Run a shell command and capture output
  shell: df -h
  register: disk_usage

- name: Print captured output
  debug:
    var: disk_usage.stdout_lines

- name: Print custom message
  debug:
    msg: "The disk usage is {{ disk_usage.stdout }}"

8.6 标签(tags)的高级用法

  • 多个标签tags: [web, config]

  • 特殊标签

    • always -- 即使指定 --tags 也会执行

    • tag_name -- 自定义

  • 命令行组合

    bash

    复制代码
    ansible-playbook site.yml --tags "web" --skip-tags "debug"

8.7 角色依赖(meta/main.yml)

mysql/meta/main.yml 中可以声明依赖其他角色:

yaml

复制代码
dependencies:
  - role: common
  - role: ntp
    ntp_server: pool.ntp.org

执行当前角色前,会自动先执行依赖角色。

8.8 ansible-vault 加密敏感数据

bash

复制代码
ansible-vault create secrets.yml
ansible-vault edit secrets.yml
ansible-playbook site.yml --ask-vault-pass

在 Playbook 中引用加密文件:

yaml

复制代码
- hosts: all
  vars_files:
    - secrets.yml

8.9 性能优化

  • 开启 SSH pipelining(减少 SSH 连接次数):

    ini

    复制代码
    # ansible.cfg
    [ssh_connection]
    pipelining = True
  • 关闭 facts 收集(如果不需要):

    yaml

    复制代码
    - hosts: all
      gather_facts: no
  • 启用 facts 缓存(使用 redis 或 json 文件):

    ini

    复制代码
    [defaults]
    fact_caching = jsonfile
    fact_caching_connection = /tmp/ansible_cache
    fact_caching_timeout = 3600

8.10 动态 Inventory(例如 AWS EC2)

bash

复制代码
pip install boto3
ansible-inventory --list -i aws_ec2.yml

示例 aws_ec2.yml

yaml

复制代码
plugin: aws_ec2
regions:
  - us-east-1
filters:
  instance-state-name: running
keyed_groups:
  - key: tags.Role
    prefix: role

九、总结与最佳实践

场景 推荐方案
少于 10 个任务,一次性使用 单个 Playbook,不用 Roles
多个环境(dev/staging/prod) 使用 group_vars + template + 条件判断
同一任务在多个 Playbook 复用 封装为 Role,上传到 Ansible Galaxy 或私有 Git
敏感密码 / API Key 使用 ansible-vault 或第三方 Secrets 管理
调试 优先使用 debug + register,再考虑 -vvv

最后的忠告

  • 写 Playbook 就像写代码,加注释使用有意义的 name保持幂等性(多次执行结果一致)。

  • 养成先 --syntax-check-C 最后正式运行的习惯。

  • 善用官方文档 ansible-doc -l 查看模块,ansible-doc copy 查看详细参数。

相关推荐
网安小白的进阶之路1 小时前
B模块 安全通信网络 第二门课IPv6与WLAN 04
网络·安全·智能路由器
yuanjj881 小时前
域格ASR平台cat1模块FTP上传、下载
运维·网络
比昨天多敲两行1 小时前
Linux 网络基础
网络
XiaoLin laile1 小时前
自主可控越来越重要,信创即时通讯为什么备受青睐
网络
CJH(本人账号)2 小时前
AI Agent 安全危机:当你的“智能助手“变成攻击者的“远程武器“
网络·人工智能·安全·ai·开源·github
猫头虎2 小时前
猫头虎AI分享|樱桃键盘Ctrl键失效解决方案:FN+PAUSE 长按10秒恢复出厂设置保姆级教程
网络·网络协议·tcp/ip·计算机外设·键盘·机械键盘·ctrl
梁辰兴2 小时前
计算机网络基础:简单网络管理协议 SNMP
网络·计算机网络·计算机·snmp·计算机网络基础·梁辰兴·简单网络管理协议
大江东去浪淘尽千古风流人物2 小时前
【VGGT】统一3D重建:单网络同时预测相机位姿、深度图、点云与3D轨迹的前馈Transformer架构深度解析
网络·数码相机·3d·transformer·slam·3d重建·cvpr2025
liulilittle2 小时前
用户态 TCP 端口转发:对 CUBIC 友好,对 BBR/KCC 收益不大
运维·网络·tcp/ip·计算机网络·信息与通信·tcp·通信