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 | 执行命令 | 临时操作,尽量少用 |
我个人建议:能用 copy 和 template 就别用 shell。因为 shell 模块不幂等,执行两次结果可能不一样。你想想看,如果同一个 Playbook 跑两次,服务挂了怎么办?
4.3 变量与模板:让配置活起来
变量是 Playbook 的灵魂。没有变量,你只能写死配置。有了变量,一套 Playbook 可以适配开发、测试、生产三套环境。
4.3.1 变量定义的位置
变量可以定义在很多地方,优先级从高到低是:
- 命令行
-e参数(最高) - Playbook 内的
vars字段 - Inventory 中的
host_vars和group_vars - 角色内的
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 就是一个功能单元,比如 nginx、java、redis。每个 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 知识体系总览
下面这张图,是我对本章内容的总结。你一看就明白:
核心要点:
- Inventory 是基础,动态优于静态
- Playbook 用 YAML,缩进是命根子
- 变量让配置灵活,模板让部署通用
- Roles 是生产环境的标配,必须掌握
我的小技巧: 每次写完 Playbook,先用 ansible-playbook --syntax-check 检查语法,再用 --check 做 dry-run。这两步能避免 90% 的部署事故。
注意: 不要在 Playbook 里写死密码或密钥。敏感信息用 Ansible Vault 加密,或者从外部密钥管理服务(如 HashiCorp Vault)获取。我曾经见过有人把数据库密码直接写在 Playbook 里,结果代码泄露到 GitHub 上,后果很严重。