Ansible 入门实践:架构、安装与生产级 Inventory 管理

公司服务器数量涨到三十多台的时候,我还在用最原始的办法:写个 for 循环,把 IP 列在脚本里,ssh 上去挨个执行。有一批机器的 SSH 端口改过,脚本跑一半断在中间,剩下的一半改了、一半没改——最后靠人工对账才收场。那次之后我把 Ansible 捡了起来,第一个动作就是把”哪些机器、分成几组、怎么连”从脚本里搬进 Inventory。

一、Ansible 是什么,为什么选它

Ansible 是自动化配置管理工具,能力可以概括成几块:

  • 远程执行:一条命令在多台主机上执行操作;
  • 配置管理:批量配置软件服务,统一管理配置和启停;
  • 事件驱动:按条件触发动作,比如”配置变了才重启服务”;
  • 任务编排:用 playbook 把一套完整部署串起来,一条命令部署一整个架构;
  • 跨平台:一套剧本兼容不同发行版,比如安装 Apache,CentOS 叫 httpd、Ubuntu 叫 apache2,Ansible 按系统类型分发。

它和手工脚本最本质的区别有两个:无 Agent(被控端什么都不用装,控制端通过 SSH 连过去执行)和幂等(同一个剧本执行多遍,结果一致,不会重复装、重复改)。这两条决定了它敢在生产环境反复跑。

架构上很简单:控制端(装了 Ansible 的那台)→ SSH → 被控端(只需要有 Python)。所以安装只需要装控制端,被控端零部署。

二、安装与配置文件

1
2
3
4
5
6
7
8
# 1. 安装 EPEL 源(CentOS 7)
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

# 2. 安装 Ansible
yum install -y ansible

# 3. 查看版本和模块路径
ansible --version

装完先改一个高频配置:跳过首次连接的指纹确认。

1
2
# /etc/ansible/ansible.cfg 中确认这一行
host_key_checking = False

不关掉它,批量操作时每台新机器都弹一次指纹确认,自动化就无从谈起。生产上这个配置由”首次统一纳管时人工核对指纹”来补安全,而不是靠交互确认。

配置文件有四个读取位置,优先级从高到低:

1
2
3
4
1. $ANSIBLE_CONFIG            # 环境变量指定的
2. ./ansible.cfg # 当前项目目录(生产用这个)
3. ~/.ansible.cfg # 用户级
4. /etc/ansible/ansible.cfg # 系统级默认

常用配置项过一遍,后面写项目配置直接参考:

1
2
3
4
5
inventory      = /etc/ansible/hosts      # 主机清单路径
forks = 5 # 默认并发数
remote_port = 22 # 远程端口
host_key_checking = False # 跳过主机指纹检查
log_path = /var/log/ansible.log # 执行日志

三、命令行工具速览

Ansible 的命令行工具各管一摊,常用的这几个:

1
2
3
4
5
6
7
ansible <host-pattern> [options]   # ad-hoc 临时命令
ansible-playbook playbook.yml # 执行剧本(生产最核心)
ansible-doc -l # 列出所有模块
ansible-doc yum # 查指定模块文档
ansible-vault encrypt vars.yml # 加密敏感变量文件
ansible-galaxy install geerlingguy.nginx # 安装社区角色
ansible-inventory --graph # 树形展示主机分组

ansible 命令的常用参数:-i 指定清单、-m 指定模块、-a 传模块参数、-f 并发数、-b 提权执行、-C 模拟执行(干跑)。

查参数这个习惯值得早点养成:ansible-doc -s <模块名> 直接列出精简参数表,比在网上翻博客可靠得多。

四、Inventory:把服务器变成结构化的资产

Inventory 就是主机清单,定义”管哪些机器、怎么连、怎么分组”。最初的形态是 /etc/ansible/hosts 这样的 INI 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 基于密钥连接(推荐)
[web_group]
web01 ansible_ssh_host=10.0.8.7
web02 ansible_ssh_host=10.0.8.8

# 组变量写法:端口、用户等组内通用配置
[db_group]
db01 ansible_ssh_host=10.0.8.51
[db_group:vars]
ansible_ssh_port=22

# 组的组:lnmp 包含 db 和 web 两个子组
[lnmp:children]
db_group
web_group

几种连接方式都见过:密钥连接是标准姿势,密码连接(ansible_ssh_pass)只建议在临时环境用——密码明文写在清单里,属于安全生产的禁区。

验证清单的方式:

1
2
ansible all -m ping -i ./hosts --list-host    # 列出所有主机
ansible-inventory -i ./hosts --graph # 树形看分组结构

五、生产环境的清单怎么组织

上面 INI 只是入门形态。真实的运维现场,Ansible 文件不应该散落在用户目录和全局配置里,而是按环境隔离、清单与变量分离,放进独立的项目目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
/opt/ops-ansible/               # 运维工具根目录下的项目
├── inventories/
│ ├── prod/ # 生产环境
│ │ ├── hosts # 主机清单
│ │ ├── group_vars/ # 组变量(自动加载,不用手动引用)
│ │ │ └── all.yml # 生产全局变量
│ │ └── host_vars/ # 单主机专属变量
│ └── test/ # 测试环境,结构与 prod 完全一致
├── playbooks/ # 剧本
├── roles/ # 角色
├── files/ # 静态分发文件
├── templates/ # Jinja2 模板
└── ansible.cfg # 项目级配置

项目级 ansible.cfg 的几个关键项,都是踩过坑之后加的:

1
2
3
4
5
6
7
8
9
10
11
12
13
[defaults]
# 默认清单指向测试环境:生产操作必须显式 -i 指定,防止手滑
inventory = ./inventories/test/hosts
remote_user = root
host_key_checking = False
roles_path = ./roles
stdout_callback = yaml # 输出用 yaml 格式,排查更清晰
gathering = smart # 智能收集 facts,大环境提速
forks = 10

[ssh_connection]
pipelining = True # SSH 连接复用,批量执行提速明显
ssh_args = -o ControlMaster=auto -o ControlPersist=300s

其中”默认清单指向测试环境”这一条是安全红线:生产操作永远要显式 -i inventories/prod/hosts,不能靠默认配置。默认连生产的环境,早晚会有人在深夜敲错命令。

生产清单的写法示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ==========================================
# 生产环境主机清单
# 环境标识:prod
# 维护人:运维团队
# ==========================================
[prod_web]
web01 ansible_host=10.0.8.7
web02 ansible_host=10.0.8.8
web03 ansible_host=10.0.8.9

[prod_nfs]
nfs01 ansible_host=10.0.8.31

[prod_db]
db01 ansible_host=10.0.8.51

[prod:children]
prod_web
prod_nfs
prod_db

[prod:vars]
env = production
ansible_ssh_private_key_file = ~/.ssh/id_rsa

所有配置文件都加文件头注释(用途、维护人、更新时间),交接的时候不用问人。

清单与变量分离是另一个核心习惯。变量不写在 hosts 文件里,而是放在对应层级的变量目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
# inventories/prod/group_vars/all.yml —— 全局变量,所有主机自动加载
timezone: Asia/Shanghai
dns_servers:
- 223.5.5.5
- 114.114.114.114

# inventories/prod/group_vars/prod_web.yml —— web 组专属
nginx_version: 1.24.0
nginx_listen_port: 80

# inventories/prod/host_vars/db01.yml —— db01 单机专属,优先级最高
mysql_version: 8.0.36
mysql_data_dir: /data/mysql

目录和文件名规则就一条:group_vars 下的文件名必须和清单里的组名一致,host_vars 下的文件名必须和主机名一致,Ansible 会自动加载,不需要任何引用。写错了不生效,而且不报错,只能靠 ansible-inventory --host 自己核。

六、从 ad-hoc 开始用起来

装好之后先用 ad-hoc(临时命令)熟悉手感——执行完即结束,不落文件:

1
2
3
4
5
6
7
8
9
10
11
# 批量看 web 节点 nginx 状态
ansible prod_web -m shell -a "systemctl is-active nginx" -i inventories/prod/hosts

# 批量设置时区
ansible all -m timezone -a "name=Asia/Shanghai" -i inventories/prod/hosts

# 批量分发公钥
ansible all -m authorized_key -a "user=root key='{{ lookup('file', '~/.ssh/id_rsa.pub') }}'" -i inventories/prod/hosts

# 看磁盘
ansible web01 -m command -a 'df -h' -i inventories/prod/hosts

输出颜色是三类:绿色(无变更)、黄色(有变更)、红色(失败)。批量操作里先扫一眼有没有红色,再决定下一步,这个习惯能拦住绝大多数”改了一半”的事故。

最后用一个规范的剧本收尾:全量主机连通性检测。

1
2
3
4
5
6
7
8
9
# playbooks/test_connect.yml
# 功能:生产环境全量主机 SSH 连通性校验
---
- name: 生产环境主机连通性检测
hosts: prod
gather_facts: no
tasks:
- name: 执行 ping 模块验证 SSH 连接正常
ansible.builtin.ping:
1
2
3
cd /opt/ops-ansible
ansible-inventory -i inventories/prod/hosts --graph # 先看清单对不对
ansible-playbook -i inventories/prod/hosts playbooks/test_connect.yml

模块名建议写全限定形式(ansible.builtin.ping),不用简写——大项目里简写会有歧义,全限定名一眼能看出模块归属。

小结

这一篇的重点其实不是安装和命令,而是 Inventory 的组织方式:环境隔离、清单与变量分离、生产操作显式指定清单。这几条定下来,后面 playbook 和 roles 才有地方安放;清单乱着放,剧本写得再漂亮也不敢在生产跑。