Ansible Playbook编写全教程:从入门到实战(附完整案例+最佳实践)

在自动化运维、批量服务器管理场景中,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 前置准备

  1. 已安装Ansible(推荐2.10+版本),验证:ansible --version
  2. 目标主机已配置SSH免密登录(或Ansible能通过密码/密钥访问);
  3. 编写工具:推荐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 核心语法规则(必记!)

  1. YAML语法约束
    • 缩进统一(推荐2个空格,禁止用Tab);
    • 键值对用key: value表示(冒号后必须加空格);
    • 列表项用- 开头(短横线后加空格);
    • 注释用#开头,仅单行有效。
  2. Play核心字段
    • name:Play名称(可选,便于日志查看);
    • hosts:目标主机(Inventory组名、IP列表,如hosts: 192.168.1.10,192.168.1.11);
    • remote_user:目标主机执行用户;
    • become:是否切换到root权限(需目标用户有sudo权限);
    • tasks:任务列表(核心字段,按顺序执行)。
  3. 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种常用方式

  1. 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
  1. 外部变量文件(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
  1. 命令行传递变量(-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配置

  1. 创建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;
    }
}
  1. 在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 最佳实践

  1. 目录结构化:将变量、模板、Playbook分离存放(如实战案例结构),便于维护;

  2. 变量规范化:核心变量(如软件版本、安装路径)统一放在vars目录,避免硬编码;

  3. 任务原子化:一个任务只做一件事(如"安装软件"和"启动服务"分开),便于调试和标签选择;

  4. 添加注释:关键步骤、变量含义添加注释,便于团队协作;

  5. 先测试后执行

    • --check(干跑模式)验证Playbook是否有语法错误、是否会执行预期操作:

      bash 复制代码
      ansible-playbook -i inventory.ini playbook.yml --check -v
    • 先在单台测试机执行,验证通过后再批量执行。

  6. 错误处理 :关键任务添加registerfailed_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 常见坑与解决方案

  1. YAML语法错误(最常见)

    • 症状:执行时提示ERROR! Syntax Error while loading YAML
    • 解决:检查缩进是否统一(用2个空格)、冒号后是否加空格、列表项是否用- 开头,推荐用VS Code的YAML插件校验。
  2. 变量引用失败

    • 症状:变量未被解析,显示{{ variable_name }}原始字符串;
    • 解决:变量引用格式正确({{ 变量名 }},中间无空格),确保变量已定义(优先级:命令行 > 剧本 > 外部文件)。
  3. 权限问题

    • 症状:任务执行失败,提示Permission denied
    • 解决:确保remote_user有足够权限,或开启become: yes(需目标用户有sudo权限,且无需输入密码)。
  4. 文件路径问题

    • 症状:模板文件、变量文件找不到,提示file not found
    • 解决:使用相对路径时,确保执行Playbook的工作目录正确,或使用绝对路径(如/opt/playbooks/templates/nginx.conf.j2)。
  5. 任务顺序问题

    • 症状:依赖任务未执行导致后续任务失败(如未安装Java就启动Java应用);
    • 解决:按"依赖顺序"排列任务(先安装依赖→创建目录→部署配置→启动服务),或用notifyhandlers实现"触发式执行"(下文扩展)。

六、扩展:高级特性(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角色
相关推荐
极客小云4 天前
【[Python自动化] 我写了一个工具,一键将几百个Word/PDF简历自动汇总到Excel,早早下班!】
自动化运维
draking8 天前
从 3 小时到 15 分钟:我们的发布效率提升 10 倍之路
自动化运维
智能运维指南10 天前
信创深化期ITSM选型:打破流程割裂,锁定全栈适配的智能方案
自动化运维·aiops·it管理·itsm·itsm厂商
饼饼饼12 天前
从 0 到 1:前端 CI/CD 实战(第二篇:用Docker 部署 GitLab)
前端·自动化运维
唐叔在学习13 天前
用python实现类AI自动执行终端指令
后端·python·自动化运维
智能运维指南13 天前
2025四大自动化运维系统选型全景:核心能力与场景适配深度解析
自动化运维·自动化运维平台·it巡检·国产自动化运维厂商·自动化运维中心
老实巴交的麻匪16 天前
(九)学习、实践、理解 CI/CD 与 DevOps:持续发布 CD,从容器镜像到生产环境
运维·云原生·自动化运维
DigitalOcean17 天前
从零开始,用 n8n 设计可扩展的自动化工作流
自动化运维·devops
饼饼饼17 天前
从 0 到 1:前端 CI/CD 实战 ( 第一篇: 云服务器环境搭建)
运维·前端·自动化运维