4. Inventory详解
Inventory(主机清单)是Ansible的“通讯录”,它定义了Playbook执行时将要管理哪些目标主机、如何连接这些主机,以及这些主机各自具备哪些属性。没有Inventory,Ansible就无法定位任何一台服务器。理解Inventory的静态格式、分组逻辑、变量继承机制以及动态Inventory的适用边界,是编写可维护、可扩展自动化脚本的基石。本章将从静态Inventory的语法细节出发,逐步深入到变量定义与嵌套组的设计模式,最后剖析动态Inventory的引入时机与典型场景。
4.1 静态Inventory格式:INI与YAML的语法精讲
静态Inventory是Ansible最基础的主机管理方式,它以纯文本文件形式存在,默认路径为 /etc/ansible/hosts,也可通过 -i 参数或 ansible.cfg 中的 inventory 配置项指定。静态Inventory支持两种主流格式:INI格式和YAML格式。INI格式简洁直观,适合快速上手;YAML格式结构更严谨,适合复杂变量嵌套和版本控制。
4.1.1 INI格式的核心语法
INI格式由方括号定义的组名、组下的主机列表以及可选的组变量段组成。主机名可以是IP地址、域名或自定义别名,若使用别名则必须通过 ansible_host 指定真实连接地址。以下是一个典型的INI格式示例:
# inventory.ini
[web]
web01 ansible_host=10.0.0.11 ansible_user=deploy
web02 ansible_host=10.0.0.12 ansible_user=deploy
[db]
db01 ansible_host=10.0.0.21
db02 ansible_host=10.0.0.22
[db:vars]
ansible_user=mysql_admin
mysql_port=3306
[all:vars]
ansible_ssh_private_key_file=/home/ops/.ssh/id_rsa
ansible_python_interpreter=/usr/bin/python3
在上述配置中,[web] 和 [db] 是普通组,[db:vars] 用于定义仅作用于db组的变量,[all:vars] 则定义全局变量。主机行中的 ansible_host、ansible_user 属于主机级变量,优先级高于组变量。INI格式的优点是书写简单,但缺点是无法表达复杂的数据结构(如列表、字典),且当主机数量庞大时文件会显得杂乱。
4.1.2 YAML格式的结构化优势
YAML格式的Inventory以 all 为根节点,使用 hosts、children、vars 三个关键字组织层级。YAML格式支持完整的变量类型,包括列表和嵌套字典,更适合与Playbook中的变量体系保持一致。以下是与上述INI等价的YAML写法:
# inventory.yml
all:
children:
web:
hosts:
web01:
ansible_host: 10.0.0.11
ansible_user: deploy
web02:
ansible_host: 10.0.0.12
ansible_user: deploy
db:
hosts:
db01:
ansible_host: 10.0.0.21
db02:
ansible_host: 10.0.0.22
vars:
ansible_user: mysql_admin
mysql_port: 3306
vars:
ansible_ssh_private_key_file: /home/ops/.ssh/id_rsa
ansible_python_interpreter: /usr/bin/python3
YAML格式的缩进必须严格一致,否则会导致解析失败。在实际项目中,建议团队统一使用YAML格式,因为它能更好地与Git版本控制配合,diff时能清晰看到变量变更。
ansible.cfg 中指定的文件。若使用 -i 参数指定目录,Ansible会尝试加载目录下所有符合格式的文件,但需注意避免同一主机在多个文件中重复定义导致变量覆盖混乱。
4.2 主机组与嵌套组:设计可扩展的组结构
主机组是Ansible批量操作的基础单元。通过将具有相同角色或环境属性的主机归入同一组,可以大幅简化Playbook的编写。嵌套组则允许组中包含其他组,形成父子关系,从而构建出“环境-角色-实例”的多级架构。
4.2.1 组的定义与成员关系
在INI格式中,使用 [组名:children] 声明子组。例如,假设我们有生产环境和测试环境,每个环境都包含web和db角色,可以这样设计:
[prod:children]
prod_web
prod_db
[test:children]
test_web
test_db
[prod_web]
web-prod-01 ansible_host=10.1.0.11
web-prod-02 ansible_host=10.1.0.12
[prod_db]
db-prod-01 ansible_host=10.1.0.21
[test_web]
web-test-01 ansible_host=10.2.0.11
[test_db]
db-test-01 ansible_host=10.2.0.21
此时,prod 组包含4台主机(2台web+2台db),test 组包含2台主机。当执行 ansible prod -m ping 时,Ansible会自动展开所有子组成员。嵌套组的好处在于:Playbook可以针对 prod 组整体操作,也可以针对 prod_web 精细化操作,无需重复定义主机列表。
4.2.2 组变量的继承与覆盖规则
变量可以定义在组级别,子组会继承父组的变量,但子组自身的变量优先级更高。Ansible的变量优先级从低到高大致为:all:vars < 父组vars < 子组vars < 主机vars。理解这一规则对排查变量覆盖问题至关重要。
| 变量定义位置 | 优先级 | 典型用途 |
|---|---|---|
| all:vars | 最低 | 全局SSH密钥路径、Python解释器路径 |
| 父组vars(如prod:vars) | 中低 | 环境级参数,如NTP服务器地址、DNS配置 |
| 子组vars(如prod_web:vars) | 中高 | 角色级参数,如HTTP端口、应用版本 |
| 主机vars(主机行内定义) | 最高 | 实例级差异,如IP地址、实例规格 |
例如,若在 all:vars 中定义了 ansible_user=root,在 prod_web:vars 中定义了 ansible_user=deploy,则prod_web组下的主机最终会使用 deploy 用户连接。这种设计允许我们为不同环境设置不同的连接凭据,而无需修改Playbook。
4.3 变量定义:从主机级到组级的完整体系
Inventory中的变量是Ansible自动化灵活性的核心。变量可以定义在主机行、组段、以及独立的 group_vars 和 host_vars 目录中。后两种方式更利于维护,因为它们将变量与Inventory文件分离,支持按目录组织变量文件。
4.3.1 内联变量与group_vars/host_vars目录
内联变量适合少量、临时性的参数,但若变量较多,推荐使用目录结构。Ansible会自动加载与Inventory同级的 group_vars/ 和 host_vars/ 目录下的YAML文件,文件名需与组名或主机名对应。例如:
inventory/
├── inventory.yml
├── group_vars/
│ ├── all.yml # 全局变量
│ ├── prod.yml # prod组变量
│ └── prod_web.yml # prod_web组变量
└── host_vars/
├── web-prod-01.yml # 特定主机变量
└── db-prod-01.yml
这种组织方式的优势在于:变量文件可以独立版本控制,且支持更复杂的YAML数据结构。例如 prod_web.yml 中可以定义:
# group_vars/prod_web.yml
nginx_version: 1.24.0
upstream_servers:
- 10.1.0.21:3306
- 10.1.0.22:3306
http_port: 8080
enable_ssl: true
4.3.2 变量优先级与覆盖策略
当同一变量在不同位置定义时,Ansible遵循严格的优先级顺序。除了前述的组继承规则外,还需注意:host_vars 中的变量优先级高于 group_vars,而Playbook中通过 vars 关键字定义的变量优先级又高于Inventory变量。这意味着Inventory变量是“默认值”,Playbook可以覆盖它们,但反过来则不行。
vars 或 vars_files 中。这样当环境变化时,只需修改Inventory;当应用升级时,只需修改Playbook,两者解耦。
4.4 动态Inventory:引入时机与典型使用场景
静态Inventory在主机数量固定、环境稳定的场景下表现良好。但当基础设施具备弹性伸缩、云原生或容器化特性时,主机列表会频繁变化,手动维护静态文件变得不切实际。此时需要引入动态Inventory,它通过外部脚本或插件实时获取主机列表。
4.4.1 动态Inventory的工作原理
动态Inventory脚本需要支持两个命令行参数:--list 用于返回所有主机和组的JSON结构,--host <hostname> 用于返回特定主机的变量JSON。Ansible执行时调用脚本获取数据,并缓存结果以减少API调用次数。现代Ansible更推荐使用云插件(如 amazon.aws.aws_ec2、kubernetes.core.k8s),它们通过Ansible Galaxy安装,配置更简洁。
# ansible.cfg 启用缓存
[inventory]
enable_plugins = host_list, script, yaml, aws_ec2, k8s
cache = yes
cache_plugin = jsonfile
cache_connection = /tmp/ansible_inventory_cache
cache_timeout = 3600
4.4.2 典型场景:云环境弹性伸缩与混合云管理
以AWS EC2为例,使用 aws_ec2 插件可以按标签、区域、实例状态等条件动态生成组。假设所有生产实例带有 Environment: prod 标签,配置如下:
# aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
filters:
tag:Environment: prod
instance-state-name: running
keyed_groups:
- key: tags.Role
prefix: role
hostnames:
- private-ip-address
执行 ansible-inventory -i aws_ec2.yml --graph 即可看到动态生成的组结构。当Auto Scaling组新增实例时,Ansible下次执行会自动发现新主机,无需人工干预。这种能力对于滚动发布、自动扩容后的配置同步至关重要。
4.5 案例拆解:混合环境下的Inventory设计实战
某互联网公司拥有自建机房(OpenStack)和公有云(阿里云)两套环境,需要统一管理约200台服务器。运维团队最初使用静态Inventory,但每逢业务高峰扩容、缩容时,手动增删主机不仅耗时且容易出错。此外,不同环境的SSH端口、用户、Python路径均不同,导致Playbook中充斥着条件判断。
初始现象与目标: 静态文件维护成本高,且无法实时反映云上实例变化。目标是通过一套Inventory方案,实现“环境自动识别、主机自动发现、变量自动匹配”。
关键输入与约束: OpenStack环境使用 openstack.cloud 插件,阿里云使用 alicloud.ecs 插件;两朵云均通过标签区分环境(env=prod、env=test);自建机房主机名遵循 hostname-role-编号 规范。
实施步骤:
- 创建
inventory.yml作为入口,通过children组合两个动态插件源。 - 为每个插件配置独立的
keyed_groups,将env标签映射为组名,将role标签映射为角色组。 - 在
group_vars/中创建prod.yml和test.yml,分别定义ansible_user、ansible_ssh_port等连接变量。 - 启用JSON文件缓存,设置超时时间为1800秒,避免每次执行都调用云API。
- 编写一个简单的
site.ymlPlaybook,针对prod组执行Nginx配置更新,验证动态发现是否生效。
结果对比: 改造前,扩容一台云主机需要手动编辑静态文件并提交PR,平均耗时15分钟;改造后,新实例启动并打上标签后,Ansible下次执行自动纳入管理,耗时0分钟。同时,由于变量按环境自动匹配,Playbook中删除了所有关于环境判断的 when 条件,代码量减少约30%。
风险与代价: 动态Inventory的调试难度高于静态文件,需要熟悉插件参数和云API返回结构。此外,若云凭证泄露,攻击者可能通过Inventory获取所有主机信息,因此必须严格管理凭证权限并定期轮换。
复盘经验: 动态Inventory并非银弹。对于固定不变的主机(如物理机、数据库主节点),仍建议保留在静态Inventory中,与动态源混合使用。同时,务必为动态Inventory配置缓存,否则每次执行Playbook都会产生API调用延迟,影响自动化效率。