4. Ansible基础与Playbook编写:Inventory管理、Playbook语法、变量与模板、Roles设计

好,咱们进入正题。Ansible 这东西,说白了就是一套「无代理的自动化框架」。你不需要在被管机器上装 agent,只要 SSH 能通、Python 环境正常,就能干活。我在券商生产环境里,几百台服务器全是靠它一把梭的。今天咱们把 Inventory、Playbook、变量模板、Roles 这四个核心模块讲透。

4.1 Inventory 管理:你的资产清单

Inventory 就是你的服务器清单。默认是 /etc/ansible/hosts 这个 INI 文件。但我个人习惯,从来不用默认路径。每个项目单独建一个 inventory 目录,清晰得多。

4.1.1 静态 Inventory 的写法

最简单的 INI 格式,长这样:

[webservers]
web01 ansible_host=192.168.1.10 ansible_user=root
web02 ansible_host=192.168.1.11 ansible_user=root

[databases]
db01 ansible_host=192.168.1.20 ansible_port=2222
db02 ansible_host=192.168.1.21

[all:vars]
ansible_ssh_private_key_file=/home/ops/.ssh/id_rsa

嗯,这里要注意:ansible_host 是真正的 IP,ansible_port 是 SSH 端口。如果你不写,默认走 22。我曾经在迁移机房时,忘了改端口,结果连不上数据库集群,排查了半小时才发现是端口没更新。避坑指南:Inventory 里的变量,优先级低于 Playbook 里的变量,但高于默认值

4.1.2 动态 Inventory:从 CMDB 拉取

券商环境里,服务器经常变。手动维护静态文件?不现实。我建议用动态 Inventory。Ansible 支持从 AWS、OpenStack、甚至你自己的 CMDB API 拉取数据。

举个例子,写一个 Python 脚本 inventory.py

#!/usr/bin/env python3
import json

def main():
    inventory = {
        "webservers": {
            "hosts": ["web01", "web02"],
            "vars": {"ansible_user": "ops"}
        },
        "databases": {
            "hosts": ["db01", "db02"]
        },
        "_meta": {
            "hostvars": {
                "web01": {"ansible_host": "10.0.0.1"},
                "web02": {"ansible_host": "10.0.0.2"}
            }
        }
    }
    print(json.dumps(inventory))

if __name__ == "__main__":
    main()

执行时加上 -i inventory.py 就行。动态 Inventory 的返回值必须包含 _meta 字段,否则 Ansible 拿不到主机变量。这个坑我踩过,当时脚本没返回 _meta,结果所有主机都连不上,报错说找不到 IP。

4.2 Playbook 语法:YAML 的优雅与陷阱

Playbook 是 YAML 格式。YAML 对缩进极其敏感。我见过太多人因为空格对齐问题,排查半天。记住:用 2 个空格缩进,不要用 Tab

4.2.1 一个完整的 Playbook 示例

---
- name: 部署交易网关
  hosts: gateways
  become: yes
  tasks:
    - name: 确保 Java 已安装
      yum:
        name: java-11-openjdk
        state: present

    - name: 复制配置文件
      template:
        src: gateway.conf.j2
        dest: /opt/gateway/conf/gateway.conf
      notify: restart gateway

  handlers:
    - name: restart gateway
      systemd:
        name: gateway
        state: restarted

你看,hosts 指定目标组,become: yes 表示提权。tasks 是任务列表,handlers 是触发器。为什么用 handlers?因为只有配置文件变了才重启服务,避免无谓的重启。我在生产环境里,所有涉及配置变更的任务,都配了 handlers,这是运维规范。

4.2.2 常用模块速查

模块名 用途 我常用的场景
copy 复制文件 部署 jar 包、脚本
template 渲染模板 生成 Nginx、Tomcat 配置
yum/apt 安装软件包 安装 JDK、Nginx
systemd 管理服务 启动、停止、重启
shell/command 执行命令 临时操作,尽量少用

我个人建议:能用 copytemplate 就别用 shell。因为 shell 模块不幂等,执行两次结果可能不一样。你想想看,如果同一个 Playbook 跑两次,服务挂了怎么办?

4.3 变量与模板:让配置活起来

变量是 Playbook 的灵魂。没有变量,你只能写死配置。有了变量,一套 Playbook 可以适配开发、测试、生产三套环境。

4.3.1 变量定义的位置

变量可以定义在很多地方,优先级从高到低是:

  1. 命令行 -e 参数(最高)
  2. Playbook 内的 vars 字段
  3. Inventory 中的 host_varsgroup_vars
  4. 角色内的 defaults/main.yml(最低)

举个例子,我在 group_vars/all.yml 里定义:

---
app_port: 8080
log_level: INFO
jvm_opts: "-Xms512m -Xmx1024m"

然后在 Playbook 里引用:

template:
  src: app.conf.j2
  dest: /opt/app/conf/app.conf

模板文件 app.conf.j2 长这样:

server.port={{ app_port }}
logging.level.root={{ log_level }}
JAVA_OPTS="{{ jvm_opts }}"

你看,Jinja2 模板用双花括号引用变量。我曾经在模板里写错了变量名,结果部署后应用启动失败。排查了半天,才发现是 app_port 写成了 app_portt。避坑指南:模板渲染后,一定要用 ansible-playbook --check 做 dry-run 检查

4.3.2 变量的高级用法

变量还可以嵌套。比如:

---
mysql:
  host: "{{ groups['databases'][0] }}"
  port: 3306
  user: "{{ vault_mysql_user }}"
  password: "{{ vault_mysql_password }}"

这里 groups['databases'][0] 是 Ansible 内置变量,表示 databases 组里的第一台主机。敏感信息用 vault 加密,后面会讲。

4.4 Roles 设计:模块化你的自动化

Roles 是 Ansible 的「模块化」方案。一个 Role 就是一个功能单元,比如 nginxjavaredis。每个 Role 有自己的目录结构,互不干扰。

4.4.1 Role 的标准目录结构

roles/
  nginx/
    tasks/
      main.yml        # 主任务文件
    handlers/
      main.yml        # 触发器
    templates/
      nginx.conf.j2   # 模板文件
    vars/
      main.yml        # 变量(高优先级)
    defaults/
      main.yml        # 默认变量(低优先级)
    meta/
      main.yml        # 依赖关系

我习惯把每个 Role 当作一个「微服务」来设计。比如 nginx Role,只负责安装和配置 Nginx,不干别的。这样复用性极高。

4.4.2 编写一个 Role 示例

假设我们要写一个 java Role,用于安装 JDK:

roles/java/tasks/main.yml

---
- name: 安装 JDK
  yum:
    name: "{{ java_package }}"
    state: present

- name: 设置 JAVA_HOME
  lineinfile:
    path: /etc/profile
    line: "export JAVA_HOME={{ java_home }}"
    create: yes

roles/java/defaults/main.yml

---
java_package: java-11-openjdk
java_home: /usr/lib/jvm/java-11-openjdk

然后在 Playbook 里引用:

---
- name: 部署交易系统
  hosts: all
  roles:
    - java
    - nginx
    - app

你看,Playbook 变得非常简洁。每个 Role 只关注自己的事。我在券商项目里,一共写了 20 多个 Role,覆盖了从操作系统初始化到应用部署的全流程。维护起来特别轻松。

4.4.3 Role 的依赖管理

Role 之间可以有依赖。比如 app Role 依赖 java Role,可以在 meta/main.yml 里声明:

---
dependencies:
  - role: java
    java_version: "11"

这样,执行 app Role 时,Ansible 会自动先执行 java Role。但要注意:依赖不要循环,否则会死循环。我曾经在测试环境里写了个 A 依赖 B、B 依赖 A 的配置,结果 Ansible 直接报错退出。

4.5 知识体系总览

下面这张图,是我对本章内容的总结。你一看就明白:

Ansible 自动化 Inventory 管理 Playbook 语法 变量与模板 Roles 设计 静态 Inventory 动态 Inventory Tasks Handlers Jinja2 模板 变量优先级 目录结构 依赖管理 图:Ansible 自动化核心模块关系图

核心要点:

  • Inventory 是基础,动态优于静态
  • Playbook 用 YAML,缩进是命根子
  • 变量让配置灵活,模板让部署通用
  • Roles 是生产环境的标配,必须掌握

我的小技巧: 每次写完 Playbook,先用 ansible-playbook --syntax-check 检查语法,再用 --check 做 dry-run。这两步能避免 90% 的部署事故。

注意: 不要在 Playbook 里写死密码或密钥。敏感信息用 Ansible Vault 加密,或者从外部密钥管理服务(如 HashiCorp Vault)获取。我曾经见过有人把数据库密码直接写在 Playbook 里,结果代码泄露到 GitHub 上,后果很严重。


专注资料整理