前言
在当今 DevOps 浪潮中,Ansible 凭借其无代理架构 、YAML 语法 以及海量模块 ,成为配置管理、应用部署、云资源编排的首选工具。而 Playbook 和 Roles 则是 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?
-
简单操作 → 使用
ansiblead-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 变量有严格的优先级,越靠后优先级越高(后面的覆盖前面的):
-
命令行
-e变量(最高) -
Role 的
vars目录 -
Role 的
defaults目录 -
Playbook 中的
vars块 -
Inventory 中的
host_vars/group_vars -
Inventory 中的
vars -
Facts 变量(
ansible_*) -
注册变量(
register) -
角色参数
-
内置变量
-
默认配置(最低)
最佳实践:
-
通用默认值放
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"
支持逻辑运算符:and、or、括号分组,以及判断变量是否存在。
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查看详细参数。