在自动化运维、批量服务器管理场景中,Ansible Playbook是核心工具------它能将复杂的操作(如软件安装、配置部署、服务启停)以"代码化"的方式定义,实现自动化、可复用、可维护的运维流程。本文从Playbook基础语法、核心组件、实战案例到优化技巧,手把手教你编写高效Playbook,新手也能快速落地批量运维需求。
一、Playbook核心认知:什么是Playbook?
Ansible Playbook是基于YAML格式的配置文件 ,用于定义一系列"任务(Task)",并指定这些任务在哪些目标主机(Inventory)上执行。相比Ansible临时命令(ansible命令),Playbook的优势在于:
- 可复用:一次编写,多次执行,适配不同环境(开发/测试/生产);
- 可维护:结构化语法,支持注释、变量、条件判断,便于团队协作;
- 功能强:支持批量部署、滚动更新、错误回滚、异步执行等复杂场景。
1.1 适用场景
- 批量安装软件(如Nginx、MySQL、Python);
- 配置文件统一部署(如Nginx虚拟主机配置、用户权限配置);
- 服务启停与状态管理(如重启Redis集群、启动应用服务);
- 系统初始化(如设置主机名、配置DNS、创建运维用户);
- 应用部署(如拉取Git代码、编译打包、启动Java应用)。
1.2 前置准备
- 已安装Ansible(推荐2.10+版本),验证:
ansible --version; - 目标主机已配置SSH免密登录(或Ansible能通过密码/密钥访问);
- 编写工具:推荐VS Code(安装YAML插件,支持语法高亮和校验)。
二、Playbook基础语法:从最小可用示例开始
Playbook的核心结构是"Play→Task→模块(Module)",一个Playbook可包含多个Play,每个Play对应一组目标主机和任务。
2.1 最小可用Playbook示例(安装Nginx)
创建文件install_nginx.yml,内容如下(注释详细解释每个字段):
yaml
# Playbook名称(可选,用于说明用途)
- name: 批量在目标主机安装并启动Nginx
# 目标主机:从Inventory中选择web_servers组(或直接指定IP列表)
hosts: web_servers
# 执行用户:在目标主机以root用户执行(需具备sudo权限)
remote_user: root
# 特权升级:是否需要sudo(yes/no,若remote_user已是root可省略)
become: yes
# 任务列表:按顺序执行,一个task对应一个操作
tasks:
# 任务1:安装Nginx(CentOS系统,用yum模块)
- name: 安装Nginx软件包
ansible.builtin.yum: # Ansible内置yum模块(管理RPM包)
name: nginx # 要安装的软件名
state: present # 状态:present=安装,absent=卸载,latest=最新版
# 任务2:启动Nginx服务并设置开机自启
- name: 启动Nginx服务并设置开机自启
ansible.builtin.service: # 管理系统服务的模块
name: nginx # 服务名
state: started # 状态:started=启动,stopped=停止,restarted=重启
enabled: yes # 是否开机自启(yes/no)
2.2 核心语法规则(必记!)
- YAML语法约束 :
- 缩进统一(推荐2个空格,禁止用Tab);
- 键值对用
key: value表示(冒号后必须加空格); - 列表项用
-开头(短横线后加空格); - 注释用
#开头,仅单行有效。
- Play核心字段 :
name:Play名称(可选,便于日志查看);hosts:目标主机(Inventory组名、IP列表,如hosts: 192.168.1.10,192.168.1.11);remote_user:目标主机执行用户;become:是否切换到root权限(需目标用户有sudo权限);tasks:任务列表(核心字段,按顺序执行)。
- Task核心字段 :
name:任务名称(必填,便于调试和日志输出);- 模块名(如
ansible.builtin.yum):指定要执行的操作(Ansible内置模块无需额外安装); - 模块参数(如
name: nginx):根据模块类型传递,不同模块参数不同。
2.3 执行Playbook
bash
# 基本执行(指定Inventory文件,若未指定则用默认/etc/ansible/hosts)
ansible-playbook -i inventory.ini install_nginx.yml
# 常用参数
ansible-playbook -i inventory.ini install_nginx.yml \
--limit web_servers # 仅执行指定组(覆盖hosts字段)
-v # 详细输出(-vvv可输出最详细日志,用于调试)
--tags "install" # 仅执行带指定标签的任务(下文会讲)
--skip-tags "start" # 跳过带指定标签的任务
三、Playbook核心组件:让Playbook更灵活、可复用
仅靠基础语法只能实现简单操作,结合变量、模板、条件判断等组件,才能编写适应复杂场景的Playbook。
3.1 变量(Variables):减少重复配置
变量用于存储可复用的值(如软件版本、配置路径、端口号),支持多种定义方式,优先级:命令行变量 > 剧本变量 > Inventory变量 > 事实变量(Ansible自动采集的目标主机信息)。
3.1.1 定义变量的3种常用方式
- Play内部定义(vars字段):
yaml
- name: 安装指定版本的Nginx
hosts: web_servers
remote_user: root
become: yes
# 变量定义
vars:
nginx_version: "1.20.1" # Nginx版本
nginx_conf_path: "/etc/nginx/nginx.conf" # 配置文件路径
tasks:
- name: 安装指定版本Nginx
ansible.builtin.yum:
name: "nginx-{{ nginx_version }}" # 引用变量:{{ 变量名 }}
state: present
- 外部变量文件(vars_files字段) : 创建变量文件
vars/nginx_vars.yml:
yaml
# vars/nginx_vars.yml
nginx_version: "1.20.1"
nginx_conf_path: "/etc/nginx/nginx.conf"
nginx_port: 80
在Playbook中引用:
yaml
- name: 安装Nginx(引用外部变量文件)
hosts: web_servers
remote_user: root
vars_files:
- ./vars/nginx_vars.yml # 相对路径或绝对路径
tasks:
- name: 安装Nginx
ansible.builtin.yum:
name: "nginx-{{ nginx_version }}"
state: present
- 命令行传递变量(-e参数):
bash
# 命令行变量优先级最高,会覆盖剧本中的变量
ansible-playbook -i inventory.ini install_nginx.yml -e "nginx_version=1.22.0"
3.1.2 事实变量(Facts):自动采集目标主机信息
Ansible会自动采集目标主机的系统信息(如操作系统版本、IP地址、内存大小),称为"事实变量",可直接在Playbook中引用:
yaml
- name: 打印目标主机信息(使用事实变量)
hosts: web_servers
tasks:
- name: 输出目标主机IP和系统版本
ansible.builtin.debug: # debug模块:用于打印变量(调试常用)
msg: "主机IP:{{ ansible_default_ipv4.address }},系统版本:{{ ansible_distribution_version }}"
3.2 模板(Templates):动态生成配置文件
当需要根据变量动态生成配置文件(如Nginx虚拟主机、MySQL配置)时,用template模块(基于Jinja2模板引擎),模板文件后缀为.j2。
实操示例:动态生成Nginx配置
- 创建Jinja2模板文件
templates/nginx.conf.j2:
nginx
# 模板文件中可引用Playbook变量和逻辑判断
worker_processes {{ ansible_processor_vcpus }}; # 引用事实变量(CPU核心数)
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
listen {{ nginx_port }}; # 引用自定义变量(端口号)
server_name {{ ansible_default_ipv4.address }}; # 主机IP作为域名
location / {
root /usr/share/nginx/html;
index index.html;
}
}
- 在Playbook中使用
template模块部署配置文件:
yaml
- name: 安装Nginx并部署动态配置
hosts: web_servers
remote_user: root
vars:
nginx_port: 8080 # 自定义端口
tasks:
- name: 安装Nginx
ansible.builtin.yum:
name: nginx
state: present
- name: 部署Nginx配置文件(从模板生成)
ansible.builtin.template:
src: ./templates/nginx.conf.j2 # 本地模板文件路径
dest: /etc/nginx/nginx.conf # 目标主机配置文件路径
mode: '0644' # 文件权限(八进制,加引号避免解析问题)
owner: root # 文件所有者
group: root # 文件所属组
- name: 重启Nginx服务(配置文件变更后生效)
ansible.builtin.service:
name: nginx
state: restarted
3.3 条件判断(Conditionals):按需执行任务
根据目标主机的状态(如操作系统类型、已安装软件版本)或变量值,决定是否执行某个任务,用when关键字。
实操示例:根据操作系统类型安装Nginx
yaml
- name: 跨系统安装Nginx(CentOS/Ubuntu)
hosts: all
remote_user: root
tasks:
# CentOS系统:用yum安装
- name: CentOS系统安装Nginx
ansible.builtin.yum:
name: nginx
state: present
when: ansible_distribution == "CentOS" # 事实变量:操作系统名称
# Ubuntu系统:用apt安装
- name: Ubuntu系统安装Nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: yes # 安装前更新apt缓存
when: ansible_distribution == "Ubuntu"
# 仅在端口不是80时,修改配置文件
- name: 非80端口时修改Nginx监听端口
ansible.builtin.template:
src: ./templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
vars:
nginx_port: 8080
when: nginx_port != 80 # 自定义变量条件
3.4 循环(Loops):批量执行重复任务
当需要执行重复操作(如批量创建用户、安装多个软件包)时,用loop关键字(替代老旧的with_items),减少代码冗余。
实操示例:批量创建用户和安装软件
yaml
- name: 批量操作示例
hosts: web_servers
remote_user: root
tasks:
# 批量安装多个软件包
- name: 安装Nginx、Redis、Git
ansible.builtin.yum:
name: "{{ item }}" # item是循环变量,代表列表中的每个元素
state: present
loop:
- nginx
- redis
- git
# 批量创建用户并设置组
- name: 创建运维用户组和用户
ansible.builtin.user:
name: "{{ item.name }}"
group: "{{ item.group }}"
shell: /bin/bash
create_home: yes # 创建家目录
loop:
- { name: "devops", group: "admin" }
- { name: "test", group: "guest" }
- { name: "app", group: "app" }
3.5 标签(Tags):灵活选择执行任务
给任务添加标签,执行Playbook时可通过--tags指定只执行某些任务,或--skip-tags跳过某些任务(调试、增量执行常用)。
实操示例:给任务添加标签
yaml
- name: 带标签的Playbook示例
hosts: web_servers
remote_user: root
tasks:
- name: 安装Nginx
ansible.builtin.yum:
name: nginx
state: present
tags: # 单个标签
- install
- nginx # 一个任务可多个标签
- name: 部署Nginx配置
ansible.builtin.template:
src: ./templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
tags:
- config
- nginx
- name: 启动Nginx
ansible.builtin.service:
name: nginx
state: started
tags:
- start
- nginx
- name: 安装Redis
ansible.builtin.yum:
name: redis
state: present
tags:
- install
- redis
执行指定标签的任务
bash
# 仅执行安装相关任务(nginx和redis的install标签)
ansible-playbook -i inventory.ini playbook.yml --tags "install"
# 仅执行nginx相关任务(所有带nginx标签的任务)
ansible-playbook -i inventory.ini playbook.yml --tags "nginx"
# 跳过启动任务,执行其他所有任务
ansible-playbook -i inventory.ini playbook.yml --skip-tags "start"
四、实战案例:完整的应用部署Playbook(Java应用)
下面以"批量部署Java应用"为例,整合变量、模板、循环、条件判断等组件,编写可直接落地的Playbook。
4.1 项目结构
python
java_app_deploy/
├── inventory.ini # 目标主机清单
├── deploy_app.yml # 主Playbook文件
├── vars/
│ └── app_vars.yml # 变量文件
└── templates/
└── app.service.j2 # 系统服务模板(用于启停Java应用)
4.2 各文件内容
1. Inventory文件(inventory.ini)
ini
# 目标主机组:web_app_servers(Java应用服务器)
[web_app_servers]
192.168.1.20
192.168.1.21
192.168.1.22
# 组变量:该组所有主机共用的变量
[web_app_servers:vars]
ansible_ssh_port=22
ansible_ssh_user=root
2. 变量文件(vars/app_vars.yml)
yaml
# 应用基础信息
app_name: "demo-app"
app_version: "1.0.0"
app_package_url: "http://xxx.xxx.xxx/demo-app-{{ app_version }}.jar" # 应用包下载地址
app_install_path: "/opt/{{ app_name }}" # 应用安装目录
app_port: 8080 # 应用端口
# Java环境配置
java_version: "1.8.0-openjdk"
java_home: "/usr/lib/jvm/jre-{{ java_version }}"
3. 系统服务模板(templates/app.service.j2)
ini
[Unit]
Description={{ app_name }} Java Application
After=network.target
[Service]
User=root
WorkingDirectory={{ app_install_path }}
ExecStart={{ java_home }}/bin/java -jar {{ app_install_path }}/{{ app_name }}-{{ app_version }}.jar --server.port={{ app_port }}
SuccessExitStatus=143
Restart=on-failure # 应用异常退出时自动重启
RestartSec=5 # 重启间隔5秒
[Install]
WantedBy=multi-user.target
4. 主Playbook(deploy_app.yml)
yaml
- name: 批量部署Java应用(安装依赖+下载包+配置服务+启动)
hosts: web_app_servers
remote_user: root
vars_files:
- ./vars/app_vars.yml # 引用变量文件
tasks:
# 步骤1:安装Java环境(依赖)
- name: 安装Java 8
ansible.builtin.yum:
name: "{{ java_version }}"
state: present
tags:
- install_java
# 步骤2:创建应用安装目录
- name: 创建应用安装目录
ansible.builtin.file:
path: "{{ app_install_path }}"
state: directory # 创建目录(不存在则创建,存在不报错)
mode: '0755'
owner: root
group: root
tags:
- create_dir
# 步骤3:下载Java应用包(若文件已存在则跳过)
- name: 下载{{ app_name }}-{{ app_version }}.jar
ansible.builtin.get_url:
url: "{{ app_package_url }}"
dest: "{{ app_install_path }}/{{ app_name }}-{{ app_version }}.jar"
mode: '0644'
force: no # 若文件已存在,不重新下载
tags:
- download_app
register: download_result # 注册任务执行结果(用于后续判断)
# 步骤4:部署系统服务配置(基于模板)
- name: 部署应用系统服务配置
ansible.builtin.template:
src: ./templates/app.service.j2
dest: "/etc/systemd/system/{{ app_name }}.service"
mode: '0644'
tags:
- config_service
# 步骤5:重新加载systemd配置(服务文件变更后)
- name: 重新加载systemd
ansible.builtin.systemd:
daemon_reload: yes
tags:
- reload_systemd
# 步骤6:启动/重启应用(若下载了新包则重启,否则仅启动)
- name: 启动{{ app_name }}应用
ansible.builtin.service:
name: "{{ app_name }}"
state: started
enabled: yes # 开机自启
when: not download_result.changed # 若未重新下载包,仅启动
tags:
- start_app
- name: 重启{{ app_name }}应用(新包已下载)
ansible.builtin.service:
name: "{{ app_name }}"
state: restarted
when: download_result.changed # 若重新下载了包,重启应用
tags:
- restart_app
# 步骤7:验证应用是否启动成功(检查端口是否监听)
- name: 验证{{ app_port }}端口是否监听
ansible.builtin.wait_for:
port: "{{ app_port }}"
delay: 5 # 延迟5秒再检查(给应用启动时间)
timeout: 30 # 超时时间30秒(启动失败则Playbook报错)
tags:
- verify_app
4.3 执行与验证
bash
# 执行部署Playbook
ansible-playbook -i inventory.ini deploy_app.yml -v
# 验证应用状态(在目标主机执行)
systemctl status demo-app
curl http://192.168.1.20:8080/health # 若应用有健康检查接口
五、Playbook编写最佳实践与避坑指南
5.1 最佳实践
-
目录结构化:将变量、模板、Playbook分离存放(如实战案例结构),便于维护;
-
变量规范化:核心变量(如软件版本、安装路径)统一放在vars目录,避免硬编码;
-
任务原子化:一个任务只做一件事(如"安装软件"和"启动服务"分开),便于调试和标签选择;
-
添加注释:关键步骤、变量含义添加注释,便于团队协作;
-
先测试后执行 :
-
用
--check(干跑模式)验证Playbook是否有语法错误、是否会执行预期操作:bashansible-playbook -i inventory.ini playbook.yml --check -v -
先在单台测试机执行,验证通过后再批量执行。
-
-
错误处理 :关键任务添加
register和failed_when,自定义错误判断逻辑:yaml- name: 检查应用健康状态 ansible.builtin.uri: url: http://localhost:{{ app_port }}/health method: GET status_code: 200 # 期望返回200状态码 register: health_check failed_when: health_check.status != 200 # 非200则任务失败
5.2 常见坑与解决方案
-
YAML语法错误(最常见):
- 症状:执行时提示
ERROR! Syntax Error while loading YAML; - 解决:检查缩进是否统一(用2个空格)、冒号后是否加空格、列表项是否用
-开头,推荐用VS Code的YAML插件校验。
- 症状:执行时提示
-
变量引用失败:
- 症状:变量未被解析,显示
{{ variable_name }}原始字符串; - 解决:变量引用格式正确(
{{ 变量名 }},中间无空格),确保变量已定义(优先级:命令行 > 剧本 > 外部文件)。
- 症状:变量未被解析,显示
-
权限问题:
- 症状:任务执行失败,提示
Permission denied; - 解决:确保
remote_user有足够权限,或开启become: yes(需目标用户有sudo权限,且无需输入密码)。
- 症状:任务执行失败,提示
-
文件路径问题:
- 症状:模板文件、变量文件找不到,提示
file not found; - 解决:使用相对路径时,确保执行Playbook的工作目录正确,或使用绝对路径(如
/opt/playbooks/templates/nginx.conf.j2)。
- 症状:模板文件、变量文件找不到,提示
-
任务顺序问题:
- 症状:依赖任务未执行导致后续任务失败(如未安装Java就启动Java应用);
- 解决:按"依赖顺序"排列任务(先安装依赖→创建目录→部署配置→启动服务),或用
notify和handlers实现"触发式执行"(下文扩展)。
六、扩展:高级特性(Handlers+Roles)
6.1 Handlers:触发式任务(配置变更后执行)
Handlers是"被动执行的任务",仅当被notify触发时才执行(如配置文件变更后,仅重启服务一次,避免多次重启)。
示例:
yaml
- name: 用Handlers实现配置变更后重启服务
hosts: web_servers
remote_user: root
tasks:
- name: 部署Nginx配置
ansible.builtin.template:
src: ./templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx # 配置变更时触发Handlers
# Handlers定义(与tasks同级)
handlers:
- name: restart nginx # 名称需与notify一致
ansible.builtin.service:
name: nginx
state: restarted
6.2 Roles:Playbook模块化(大型项目必备)
当Playbook越来越复杂时,用Roles将任务、变量、模板按功能拆分(如web角色、db角色、app角色),实现模块化复用。
Roles目录结构:
python
roles/
├── nginx/ # 角色名
│ ├── tasks/ # 任务文件(main.yml是入口)
│ ├── vars/ # 角色专属变量
│ ├── templates/ # 角色专属模板
│ ├── handlers/ # 角色专属Handlers
│ └── defaults/ # 默认变量(优先级最低)
└── java/ # 另一个角色
├── tasks/
└── ...
在Playbook中引用Roles:
yaml
- name: 用Roles部署Web服务
hosts: web_servers
roles:
- nginx # 引用nginx角色
- java # 引用java角色