第4章:Playbook基础:YAML语法精讲、Playbook核心结构、Tasks与Handlers、变量定义与引用

好,咱们进入正题。Playbook 是 Ansible 的灵魂,说白了,它就是你的自动化剧本。你写好了剧本,Ansible 这个演员就按你的要求去执行。这一章,我会带你从零开始,把 Playbook 的底子打扎实。

4.1 YAML 语法精讲:别让格式坑了你

Playbook 是用 YAML 写的。YAML 的语法,嗯,其实很简单,但坑也不少。我在项目中见过太多人因为缩进问题排查半天,最后发现少了个空格。

核心规则就三条:

  • 缩进:用空格,别用 Tab。我习惯用两个空格缩进,清晰明了。
  • 键值对:冒号后面必须跟一个空格。比如 name: 安装nginx,冒号后的空格不能省。
  • 列表:用短横线加空格开头。比如 - 192.168.1.1

重要提醒:YAML 对缩进极其敏感。同一个层级的元素,缩进必须一致。我曾经有个同事,把 tasks 写成了两格缩进,结果 Ansible 直接报语法错误,查了半小时才发现。

来看个完整的 YAML 示例:

---
# 这是一个注释
web_servers:
  - host: web01
    ip: 192.168.1.10
    ports:
      - 80
      - 443
  - host: web02
    ip: 192.168.1.11
    ports:
      - 80

你看,web_servers 是一个字典,它的值是一个列表。列表里的每个元素又是一个字典。这种嵌套结构在 Playbook 里非常常见。

小技巧:我写 YAML 时,习惯用 --- 开头,表示文档开始。虽然不写也行,但加上更规范,也方便后续用工具合并文件。

4.2 Playbook 核心结构:剧本的骨架

一个标准的 Playbook,长什么样?我直接给你看个例子:

---
- name: 配置Web服务器
  hosts: webservers
  become: yes
  vars:
    http_port: 80
    max_clients: 200

  tasks:
    - name: 确保nginx已安装
      apt:
        name: nginx
        state: present

    - name: 启动nginx服务
      service:
        name: nginx
        state: started
        enabled: yes

结构其实很清晰:

  • name:这个 Play 的名字,方便你识别。我建议每个 Play 都起个有意义的名字。
  • hosts:目标主机,可以是主机组名,也可以是 IP。
  • become:是否提权。设为 yes 表示用 root 权限执行。
  • vars:变量定义区,后面会细讲。
  • tasks:任务列表,这是核心中的核心。

你想想看,这个结构是不是很像一个剧本?hosts 是演员,tasks 是台词,vars 是道具。Ansible 就是导演,按你的剧本一步步执行。

4.3 Tasks 与 Handlers:任务与触发器

Tasks 就是你要执行的操作。每个 task 都有一个 name 和一个具体的模块调用。比如:

tasks:
  - name: 拷贝nginx配置文件
    copy:
      src: /local/nginx.conf
      dest: /etc/nginx/nginx.conf
    notify: 重启nginx

这里有个关键点:notify。它是什么意思?

Handlers 是特殊的 tasks,它们只在被 notify 触发时才执行。而且,就算你 notify 了多次,Handler 也只会在所有 tasks 执行完后,运行一次。

为什么要这样设计?我举个例子。你在部署时,可能改了配置文件,也可能没改。如果每次拷贝都重启服务,那效率太低了。Handler 就是解决这个问题的——只有配置真正变了,才触发重启。

handlers:
  - name: 重启nginx
    service:
      name: nginx
      state: restarted

注意:Handler 的执行顺序是按它们在 handlers 中定义的顺序,而不是 notify 的顺序。我曾经踩过这个坑,以为先 notify 的会先执行,结果 Handler 是按定义顺序来的。所以,写 Handler 时,把依赖关系理清楚。

4.4 变量定义与引用:让剧本活起来

变量是 Playbook 的灵魂。没有变量,你的剧本就是死的。有了变量,一个剧本可以适配不同的环境。

变量定义的方式有很多:

  • 在 Playbook 中定义:用 vars 关键字。
  • 在文件中定义:用 vars_files 引入外部文件。
  • 在主机清单中定义:在 inventory 文件里给主机或组设置变量。
  • 在命令行传入:用 --extra-vars 参数。

来看个综合例子:

---
- name: 部署应用
  hosts: appservers
  vars:
    app_version: "1.2.3"
    app_port: 8080
  vars_files:
    - "vars/{{ env }}.yml"

  tasks:
    - name: 打印应用版本
      debug:
        msg: "当前版本是 {{ app_version }},端口是 {{ app_port }}"

变量引用用双花括号 {{ }}。注意,如果变量是字符串,最好用引号包起来,避免 YAML 解析出错。

变量优先级:记住一个原则——越具体的变量,优先级越高。命令行传入的 --extra-vars 优先级最高,其次是 Playbook 中定义的 vars,然后是主机清单中的变量。我建议,通用变量放在主机清单,环境相关变量放在 vars_files,临时覆盖用 --extra-vars

我个人习惯,把变量按环境拆分到不同文件。比如 dev.ymlprod.yml,然后在 Playbook 里用 {{ env }} 动态加载。这样,一个 Playbook 就能适配开发、测试、生产三个环境,省心省力。

4.5 实战小技巧:写 Playbook 的避坑指南

嗯,这里我要分享几个我踩过的坑:

  • 缩进问题:编辑器设置显示空格,别用 Tab。我推荐 VS Code 加 YAML 插件,能自动检查缩进。
  • 变量名冲突:别用 hostport 这种太通用的名字,容易和 Ansible 内置变量冲突。我习惯加个前缀,比如 myapp_port
  • Handler 不触发:检查一下 notify 的名字是否和 Handler 的 name 完全一致,大小写和空格都不能错。
  • 调试用 debug:不确定变量值对不对?用 debug 模块打印出来看看,比猜靠谱。

我的习惯:写 Playbook 时,先用 --check 模式跑一遍,看看会执行什么操作,确认无误后再真正执行。这个习惯帮我避免了好几次生产事故。

好了,这一章的内容就到这里。YAML 语法、Playbook 结构、Tasks 和 Handlers、变量定义与引用,这四个点你掌握了,写 Playbook 的基础就算打牢了。下一章,我们会深入模块的使用,到时候你会看到,这些基础知识点是如何组合起来解决实际问题的。