Ansible 剧本精粹 - 编写你的第一个 Playbook

Star英

关注

阅读 13

06-10 09:00

Ansible 剧本精粹 - 编写你的第一个 Playbook

如果说 Ansible Ad-Hoc 命令像是你对厨房里的助手发出的零散口头指令(“切个洋葱”、“烧开水”),那么 Playbook 就是一份完整、详细、写在纸上的菜谱。它列明了所有需要的“食材”(变量),详细的“烹饪步骤”(任务),甚至还有一些“特殊处理程序”(处理器),可以指导任何人(或 Ansible)精确地烹制出一道(甚至一桌)美味佳肴(即完成复杂的系统配置)。

Playbook 是 Ansible 实现“配置即代码 (Configuration as Code)”的核心。

Playbook 基础结构

Playbook 使用 YAML (YAML Ain't Markup Language) 格式编写。YAML 以其简洁、人类可读性强而著称。你需要了解一些基本的 YAML 语法:

  • 缩进: YAML 使用空格(通常是 2 个或 4 个,但必须在同一层级保持一致)来表示层级关系,非常重要!
  • 列表/数组: 以 - (短横线加空格) 开头。
  • 字典/映射: 以 key: value (冒号后有空格) 的形式表示。

下面是一个最小化的 Playbook 结构示例,用于确保 webservers 组中的服务器都安装并启动了 Nginx:

# my_nginx_playbook.yaml
--- # YAML 文档开始的标志 (可选,但推荐)
- name: Configure Nginx Web Servers # 这是第一个 "Play" (剧目) 的名称
  hosts: webservers                 # 此 Play 针对 Inventory 中的 'webservers' 组
  become: true                      # 表示需要提权 (例如 sudo) 来执行任务

  tasks:                            # 此 Play 包含的任务列表
    - name: Ensure nginx package is present and updated # 第一个任务的名称
      ansible.builtin.apt:          # 使用的模块 (推荐使用 FQCN - 完全限定集合名称)
        name: nginx
        state: present              # 确保 nginx 包是 'present'(已安装) 状态
        update_cache: yes           # 执行前更新 apt 缓存

    - name: Ensure nginx service is started and enabled # 第二个任务的名称
      ansible.builtin.service:
        name: nginx
        state: started              # 确保服务是 'started'(已启动) 状态
        enabled: yes                # 确保服务是 'enabled'(开机自启) 状态

解读核心组件:

  • ---: YAML 文档的开始标记(一个 Playbook 文件可以包含多个 YAML 文档,每个文档以 --- 开始,以 ... 结束,但通常一个文件就是一个 Playbook,只包含一个 ---)。
  • - (短横线): YAML 中列表项的开始。最外层的列表项代表一个 Play (剧目)
  • Play (剧目): Playbook 中的一个逻辑执行单元。它将一组任务映射到 Inventory 中的一组主机。一个 Playbook 可以包含多个 Play,它们会按顺序执行。
  • name: 对这个 Play 的描述性名称,方便阅读和理解。
  • hosts: 指定这个 Play 作用于 Inventory 中的哪些主机或主机组。可以是 all,某个组名,某个主机名,或者更复杂的模式。
  • become: true/false: 是否需要提升权限来执行 Play 中的任务。可以设在 Play 级别(对所有任务生效)或 Task 级别(只对该任务生效)。
  • tasks: 一个列表,包含了在此 Play 中要按顺序执行的任务 (Task)
  • Task (任务): Playbook 中的最小执行单元,定义了一个要完成的动作。
  • name: 对这个 Task 的描述性名称。
  • module_name: {arguments}: 指定要调用的 Ansible 模块 (Module) 及其参数。例如,ansible.builtin.apt 是用于 Debian/Ubuntu 系统包管理的模块。推荐使用 FQCN (Fully Qualified Collection Name)ansible.builtin.apt 而非简单的 apt,这能提高可读性和避免潜在的模块名冲突,尤其是在使用 Ansible Collections 时。

模块再探 (Modules Revisited)

我们之前提到,模块是 Ansible 完成实际工作的“工具”。Ansible 拥有庞大的模块库,覆盖了系统管理的方方面面:包管理、服务控制、文件操作、模板渲染、用户管理、命令执行、云资源管理等等。你可以在 Ansible 官方文档中查找和学习具体模块的用法。

变量的使用 (Using Variables)

硬编码值(如包名、用户名、端口号)会使 Playbook 难以复用和维护。变量 (Variables) 允许我们将这些值参数化。

  • 定义变量的位置:
  • Playbook 的 vars: 部分 (Play 级别)。
  • 通过 vars_files: 导入外部变量文件。
  • Inventory 文件中 (主机变量 host_vars/hostname.yml,组变量 group_vars/groupname.yml)。
  • 命令行通过 -e "var_name=value" 传递。
  • 通过 register 关键字将一个任务的输出注册为一个变量。
  • 使用变量: 在 Playbook 中,使用 Jinja2 模板语法 {{ variable_name }} 来引用变量。
  • 示例:

---
- name: Install packages and create user using variables
  hosts: all
  become: true
  vars: # 在 Play 级别定义变量
    install_packages:
      - vim
      - htop
      - curl
    user_to_create: alice
  tasks:
    - name: Install specified packages
      ansible.builtin.apt:
        name: "{{ item }}" # 使用 item 变量,通常与 loop 结合
        state: present
      loop: "{{ install_packages }}" # 循环遍历 install_packages 列表

    - name: Create user {{ user_to_create }}
      ansible.builtin.user:
        name: "{{ user_to_create }}" # 使用 user_to_create 变量
        state: present
        shell: /bin/bash

Handlers 与通知 (Handlers and Notifications)

有些任务(比如重启服务)我们不希望每次运行 Playbook 都执行,而只希望在相关的配置文件发生改变时才执行,并且即使有多个任务都请求重启,也只重启一次。这就是 Handlers (处理器) 的作用。

  • 定义 Handlers: 在 Playbook 中与 tasks: 同一级,使用 handlers: 块定义。Handler 本身也是一个 Task。
  • 触发 Handlers (通知): 在一个 Task 执行后,如果该 Task 的状态是 changed(即它确实对系统做了修改),并且该 Task 定义了 notify: 指令,那么对应的 Handler 就会被“通知”。
  • 执行时机: 被通知的 Handler 不会立即执行,而是在当前 Play 中所有常规 Task 执行完毕后,才会按照它们被定义的顺序执行一次(即使被多次通知也只执行一次)。
  • 示例:

---
- name: Configure web server and restart if needed
  hosts: webservers
  become: true
  tasks:
    - name: Ensure nginx is installed
      ansible.builtin.apt:
        name: nginx
        state: present

    - name: Copy nginx configuration file
      ansible.builtin.template: # 通常配置文件用 template 模块
        src: my_nginx_config.conf.j2 # 源模板文件 (下一节讲)
        dest: /etc/nginx/sites-available/my_app
      notify: # 如果这个任务导致文件内容改变 (changed),则通知下面的 handler
        - Restart nginx service

    - name: Enable nginx site
      ansible.builtin.file:
        src: /etc/nginx/sites-available/my_app
        dest: /etc/nginx/sites-enabled/my_app
        state: link
      notify: Restart nginx service # 如果符号链接状态改变,也通知重启

  handlers: # 定义处理器
    - name: Restart nginx service
      ansible.builtin.service:
        name: nginx
        state: restarted
      listen: "Restart nginx service" # 可选,让 notify 更明确地指向 handler 的 name

模板 (Templates - Jinja2)

当我们需要根据变量动态生成配置文件内容时,就需要使用模板 (Templates)。Ansible 使用 Jinja2 作为其模板引擎(与 Python Flask/Django Web 框架中使用的类似)。

  • 工作方式: 你创建一个模板文件(通常以 .j2 结尾,如 nginx.conf.j2),在其中使用 Jinja2 语法嵌入变量、循环、条件判断等。ansible.builtin.template 模块会读取这个模板文件,用实际的变量值替换掉占位符,然后将渲染后的文件内容复制到目标节点上。
  • Jinja2 模板示例 (my_nginx_config.conf.j2):

# my_nginx_config.conf.j2
server {
    listen {{ http_port | default(80) }}; # 使用变量 http_port,如果未定义则默认为 80
    server_name {{ ansible_fqdn }};       # ansible_fqdn 是 Ansible 自动收集的 Fact 变量

    root /var/www/{{ app_name | default('default_app') }}/html;
    index index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }

    # 如果定义了 php_fpm_socket 变量,则包含 PHP 配置
    {% if php_fpm_socket is defined %}
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:{{ php_fpm_socket }};
    }
    {% endif %}
}

  • 解释:
  • {{ variable_name }}: 引用变量。
  • | default(value): Jinja2 过滤器,如果变量未定义,则使用默认值。
  • ansible_fqdn: Ansible 在执行 Playbook 前会自动收集目标主机的很多信息(称为 Facts),这些 Facts 可以作为变量直接使用。
  • {% if ... %} ... {% endif %}: 条件判断。

运行 Playbook

编写好 Playbook 后,使用 ansible-playbook 命令来执行它。

  • 命令 (输入):

ansible-playbook -i myinventory.ini my_nginx_playbook.yaml [-u remote_user] [--become] [-K] [-v]

  • 常用选项:
  • -i <inventory_file>: 指定 Inventory 文件。
  • -v, -vv, -vvv, -vvvv: 增加输出的详细程度 (verbose)。
  • --check-C: 演练模式 (Dry Run)。模拟执行,不会实际修改目标主机,但会报告可能发生的变更。强烈建议在实际执行前使用!
  • --limit <host-pattern>: 只在 Inventory 中匹配此模式的主机上运行。
  • --list-hosts: 列出 Playbook 将要执行的目标主机,不实际运行。
  • --list-tasks: 列出 Playbook 中将要执行的所有任务。
  • -K--ask-become-pass: 提示输入提权密码(如 sudo 密码)。
  • 输出解读 (概念性): Ansible Playbook 执行完毕后,会输出一个 PLAY RECAP,总结了每个主机的执行情况:

PLAY RECAP *********************************************************************
web1.example.com           : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

  • ok: 成功执行且对系统做出更改的任务数。
  • changed: 成功执行且对系统做出更改的任务数。
  • unreachable: 无法连接的主机数。
  • failed: 执行失败的任务数。
  • skipped: 因条件不满足而被跳过的任务数。

总结

Playbook 是 Ansible 自动化的核心,它使用人类可读的 YAML 格式,通过组织有序的 Play 和 Task,结合强大的模块、变量、处理器和模板功能,使我们能够定义和执行复杂、可重复且版本可控的配置管理流程。这真正体现了“配置即代码”的思想。

掌握了 Playbook 的基本编写,你就拥有了自动化大部分日常运维任务的能力。但是,当配置逻辑变得越来越复杂,Playbook 文件越来越长时,如何有效地组织和复用我们的 Ansible 代码呢?答案就是 Ansible Roles

在下一篇博客中,我们将深入探讨 Ansible Roles 的概念、目录结构和使用方法,以及更高级的 Inventory 管理技巧,让你的 Ansible 技能更上一层楼。敬请期待!

精彩评论(0)

0 0 举报