☰
Ansible -i 参数全解:主机清单与动态 Inventory 实战指南
2026/10/10 18:38:51 网站建设 项目流程

打从我开始用 Ansible 做自动化运维起,-i这个参数就一直在那儿,但真正把它吃透,花了我不少时间。早期排错的时候,经常是ansible-playbook执行后返回一屏黄色警告,要么 "No hosts matched",要么连过去的主机压根不是我以为的那台。排查到最后,十有八九问题都出在 inventory 的指定和解析上,而-i正是入口。这篇东西不打算给你念手册,就照着我在实际项目里踩过的坑、验证过的写法,把-i参数的逻辑、各种 inventory 组织方式和排错经验一次讲透。

不管你是在用 Ansible 管几十台虚拟机,还是在云环境里折腾动态主机列表,只要绕不开ansible-playbook,-i就值得你花十分钟彻底搞明白。

1.-i参数背后的运行逻辑:inventory 到底是什么

很多人对-i的理解停留在"指定一个文件",但实际它的运行逻辑比这丰富得多。先明确一个概念:Ansible 本身并不知道要连哪些机器,它需要在执行前拿到一份"主机清单",这份清单就是 inventory,中文常叫主机清单、资产清单。-i是--inventory的简写,干的事情就是告诉 Ansible:"你的通讯录在哪儿,去那儿找人。"

1.1 没写-i时,Ansible 默认去哪里找主机

刚接触 Ansible 的时候,我一度以为只要写完 playbook,命令跑起来就能自动连上服务器。后来才发现,如果你什么都不指定,Ansible 会按固定顺序去查找 inventory。

默认情况下,Ansible 会读取/etc/ansible/hosts这个文件,前提是你本机装了 Ansible 的标准配置。更常见的情况是,项目里放了一个ansible.cfg,里面会有一段类似这样的配置:

[defaults] inventory = ./inventory/hosts

只要ansible.cfg在当前目录,Ansible 就会自动读取它,然后用里面定义的 inventory 路径。这里的优先级关系可以理解为:命令行-i参数 >ansible.cfg中的 inventory 配置 >/etc/ansible/hosts默认文件。

也就是说,-i是最直接的指定方式,它的优先级最高。实际项目里我不建议过分依赖ansible.cfg里的默认 inventory,因为一旦多人协作、多环境切换,配置文件里的路径很容易把别人带沟里。反之,在执行命令时显式用-i,别人一看命令就知道这次操作的是哪批机器。

1.2-i的三种传参姿势:文件、主机列表、目录与脚本

-i后面能跟的东西,比大部分人以为的多。我按使用频率排个序。

第一种,指定一个 inventory 文件,这是最普通的用法。比如项目里建了inventory/hosts,执行的时候:

ansible-playbook -i inventory/hosts deploy.yml

这个文件可以是 INI 格式,也可以是 YAML 格式,甚至可以是一个压缩包里的某个文件,只要 Ansible 能解析就行。

第二种,直接给主机列表。注意这里有个非常经典的坑。Ansible 允许你用逗号分隔的字符串作为 inventory,像这样:

ansible -i 'web1,web2,' all -m ping

最后的那个逗号,不是手误,而是必须的。如果你只写一个主机,-i 'web1,'结尾不加逗号,Ansible 会认为web1是一个文件路径,然后报错说找不到这个文件。这个细节我见过不少新手栽过跟头,包括我自己刚上手的时候也被坑过一回。

第三种也是现代 Ansible 项目的主流方式,-i指向一个目录,或者指向一个可执行的动态 inventory 脚本。目录的话,Ansible 会递归加载目录下所有符合规则的文件。脚本的话,Ansible 会执行它并解析输出结果。后面我会专门开一节细讲这两种模式。

2. 四种主流的 inventory 写法:从 INI 到动态清单

有读者可能觉得 inventory 不就是个文本文件嘛,往里写 IP 然后用冒号分组。真这么简单就好了。实际项目里,inventory 的写法直接决定了你组织机器的方式,也决定了你后面变量管理的复杂度。

2.1 INI 格式:经典写法里的几个容易踩的细节

INI 格式是从旧版本 Ansible 一路用过来的,写起来松弛,但也正是因为松弛,很多细节容易出错。看一个典型例子:

[web] 192.168.1.10 192.168.1.11 ansible_port=2222 ansible_user=deploy web01.domain.com ansible_host=10.0.0.5 [db] db01 ansible_host=192.168.1.20 ansible_user=root [all:vars] ansible_python_interpreter=/usr/bin/python3

这里面有几个值得注意的点。第一,inventory 里的名字不一定是真实主机名或 IP,你可以给主机起一个"别名",比如db01,然后在同一行用ansible_host告诉 Ansible 真正的连接地址。这样做的意义是:连接 IP 变了,你只需要改 inventory 里的ansible_host,playbook、角色里的主机名引用完全不用动。

第二,行内变量用key=value形式跟在主机后面,这些变量只对当前主机生效。如果你要统一设置一组密码或用户,放到组级别的[web:vars]或者[all:vars]里会更合适。

第三,别用特殊字符做组名。组名里有中横线、点、空格的话,Ansible 2.4 之后的版本会给出警告:"Invalid characters were found in group names"。虽然大部分情况命令还能跑,但后患无穷,特别是在 group vars 文件命名和动态 inventory 引用时容易出问题。

2.2 YAML 格式:更清晰,也更适合版本管理

YAML 格式是官方越来越推荐的写法,因为结构清楚、嵌套直观,而且很多团队会用 Git 管理 inventory,YAML 的 diff 体验比 INI 好太多。

同样的主机清单,用 YAML 写是这样:

all: children: web: hosts: 192.168.1.10: 192.168.1.11: ansible_port: 2222 ansible_user: deploy web01: ansible_host: 10.0.0.5 db: hosts: db01: ansible_host: 192.168.1.20 ansible_user: root vars: ansible_python_interpreter: /usr/bin/python3

YAML 的层级关系非常明确:all是总根,children下面挂组,组下面才是主机。用这种格式最舒服的一点是,每个主机的变量可以单独成段,以后加变量的时候很难搞乱。

注意,YAML inventory 最外层必须是all,这个结构不能变。哪怕你只想写一个组,也得从all -> children -> 你的组这样套下来。

2.3 一个目录搞定多环境管理:-i指向目录的妙用

单一 inventory 文件在项目小的时候很够用,但到了要区分 dev、staging、prod 多套环境的时候,一个文件塞所有内容会越来越难维护。Ansible 有一个很实用的能力:-i可以接一个目录,Ansible 会加载目录下多个 inventory 文件并合并成一份完整的主机清单。

我实际项目的目录结构长这样:

inventory/ ├── prod/ │ ├── hosts.yml │ └── group_vars/ │ └── all.yml ├── staging/ │ ├── hosts.yml │ └── group_vars/ │ └── all.yml └── dev/ ├── hosts.yml └── group_vars/ └── all.yml

执行的时候显式指定环境目录:

ansible-playbook -i inventory/prod deploy.yml ansible-playbook -i inventory/staging deploy.yml

目录加载有两条规则需要记住。第一,Ansible 会递归检索目录下后缀为.ini、.yml、.yaml的文件并解析。隐藏文件会被忽略。第二,目录下如果有可执行文件,Ansible 会把它当作动态 inventory 来执行。这个规则既是便利也是坑:如果你不小心把一个普通脚本放进了 inventory 目录且给了执行权限,Ansible 会尝试运行它来获取主机信息,结果可想而知。

对多环境管理来说,目录形式配合group_vars目录是绝配。每个环境下的hosts.yml只写主机归属关系,而环境的差异(比如 SSH 端口、登录用户、域名后缀)全部放在group_vars/all.yml里,相互独立、互不干扰。

2.4 动态 inventory:云环境下-i的进化用法

如果你觉得 inventory 只能是个静态文件,那就错过了 Ansible 最强大的一块拼图。所谓动态 inventory,是指-i指向的对象不是一个静态文件,而是一个可执行的程序或 YAML 插件配置文件,它会在每次执行时动态获取当前环境下的主机列表。

传统脚本方式长这样:-i指定一个 Python 脚本,脚本执行后输出一段标准 JSON,Ansible 解析这段 JSON 得到主机分组。JSON 的根结构一般是按照组名组织,比如:

{ "web": { "hosts": ["10.0.0.10", "10.0.0.11"] }, "_meta": { "hostvars": {} } }

Ansible 2.4 之后更推荐走 inventory plugin 的路线,配置方法更简洁,拿 AWS 举例,写一个aws_ec2.yml:

plugin: aws_ec2 regions: - ap-northeast-1 keyed_groups: - key: tags.Role prefix: tag

然后在ansible.cfg里启用对应的插件:

[inventory] enable_plugins = aws_ec2, yaml, ini, host_list

执行时同样用-i指向这个 YAML 文件:

ansible-playbook -i aws_ec2.yml deploy.yml

Ansible 会根据配置文件里的过滤条件,实时拉取云端实例信息作为主机清单。这套流程在基础设施频繁伸缩的环境里是刚需,因为没有任何一份静态文件能追上云主机创建销毁的速度。核心理解在于:-i不仅仅是一个文件路径参数,它是 Ansible 获取主机清单的"通道",通道那头可以是静态文件、目录,也可以是云平台的 API。

3. 组合玩法:多 inventory 合并、变量覆盖与 Playbook 完整实战

单文件、单目录大家都会用,真正拉开效率差距的,是对多个 inventory 来源进行合并,以及对变量覆盖关系的把握。这一节说几个我在真实项目里反复验证过的场景。

3.1 多个 inventory 合并时,到底谁覆盖谁

Ansible 支持在一条命令里多次使用-i,也支持用逗号拼接多个路径。当存在多个 inventory 来源时,Ansible 会把它们合并成一个整体。

ansible-playbook -i inventory/base -i inventory/prod deploy.yml

这时候有个关键问题:如果两个 inventory 里都定义了同一个主机或同一个组变量,谁说了算?答案是:后面的覆盖前面的。Ansible 在加载 inventory 时是顺序处理的,后面的来源遇到同名的主机或组,会对变量做合并,后出现的值覆盖先出现的值。

需要小心的是,"后面的覆盖前面的"只适用于主机变量和组变量这个层面。如果你两个 inventory 中把同一组 A 定义成了不同的 children 组合,Ansible 会做合并而不是替换,也就是最终组 A 会同时拥有两份 children 的交集集合。

这里还有一层容易混淆的点:inventory 变量和 playbook 变量的优先级。inventory 里定义的变量再高,也高不过 playbook 里通过vars关键字定义的变量,更别说命令行-e传入的 extra vars 了。完整的优先级顺序从高到低大概是:extra vars > playbook vars > playbook host_vars > inventory host vars > inventory group vars。我在项目里就把所有"环境差异化配置"放在 inventory 的 group vars 里,默认值写死在 playbook 层,这样既能覆盖,又不会因为 inventory 缺失导致整个剧本跑不起来。

3.2 利用:children和:vars的组合,优雅管理分层架构

实际运维里,主机分组通常不只一层。比如有 web、db 两组机器,但业务上它们都属于prod环境,你总不能给每组都配上环境的通用变量。这时候组嵌套就派上用场了。

INI 格式的写法比较老派:

[web] web01 web02 [db] db01 [prod:children] web db [prod:vars] deploy_env=production log_level=info

YAML 格式的嵌套更直观:

all: children: prod: children: web: hosts: web01: web02: db: hosts: db01: vars: deploy_env: production log_level: info

这样设置之后,web01和db01同时属于prod组,因此能继承prod:vars里的变量。在执行命令时,你可以用ansible-playbook -i inventory/prod/hosts.yml --limit web精准圈定这一组主机,同时它们依然带着prod组的变量。组嵌套的价值就是让"环境纬度"和"角色纬度"解耦,各管各的,不互相污染。

3.3 一个完整的ansible-playbook -i实战案例

理论和技巧讲再多,不如来一个完整可复用的实操案例。假设我有一个简单的 Web 服务,需要部署到 dev 和 prod 两套环境。项目结构如下:

project/ ├── ansible.cfg ├── inventory/ │ ├── dev/ │ │ ├── hosts.yml │ │ └── group_vars/ │ │ └── all.yml │ └── prod/ │ ├── hosts.yml │ └── group_vars/ │ └── all.yml ├── playbooks/ │ └── deploy.yml └── roles/ └── webapp/

inventory/dev/hosts.yml内容:

all: children: web: hosts: dev-web01: ansible_host: 192.168.56.11

inventory/dev/group_vars/all.yml:

app_version: 1.3.2 app_conf_path: /etc/myapp/conf.d

deploy.yml的核心内容简化如下:

- name: Deploy web application hosts: web gather_facts: true vars: app_version: "1.0.0" roles: - webapp

执行命令:

ansible-playbook -i inventory/dev deploy.yml

注意,这里没有写playbooks/deploy.yml全路径,因为我在项目根目录执行,Ansible 通过ansible.cfg定位到了 playbook 目录,而-i则精确指定了 dev 环境。这样一条命令就能把 dev 所有 web 主机拉到web组里完成部署。

开发环境验证通过后,切到生产环境,同样只改-i参数就行:

ansible-playbook -i inventory/prod deploy.yml

因为 prod 的 host vars 和 group vars 各自独立,所以不会出现"dev 的 IP 被带到 prod"这类事故。我在真实项目中甚至还会在 prod 的group_vars/all.yml里加上ansible_host校验之类的保护措施,比如记录部署窗口的标记,让剧本在要求环境不匹配时直接 fail-fast。

4. 常见报错排查与验证技巧

-i参数本身没什么高深语法,但一个命令写错,报错信息千奇百怪。这一节就专门给一份排查清单,都是从我自己和身边同事的运维事故里总结出来的。

4.1 高频错误速查表

报错现象最可能原因解决方式
[WARNING]: No hosts matched, nothing to doplaybook 里指定的 hosts 组在 inventory 中不存在用ansible -i xx --list-hosts确认实际分组名
[WARNING]: provided hosts list is emptyinventory 文件为空,或-i指定的路径下没有任何可解析文件检查文件后缀和内容合法性
ERROR! The inventory file ... marked as executable but failed to execute目录下的脚本没有正确的执行权限或脚本本身报错chmod +x 脚本,并在本地手工执行脚本排查
[WARNING]: Invalid characters were found in group names组名中带有中横线、空格、点等特殊字符改用下划线,组名统一小写
"msg": "Failed to connect to the host via ssh"inventory 里主机的地址、端口、用户名配置不对逐项检查ansible_host、ansible_port、ansible_user
ERROR! Specified group 'web' cannot be found命令里--limit web,但 inventory 中没有web组检查 inventory 文件的组定义拼写

有个报错我得单独拎出来强调一下:No hosts matched。这个信息极具迷惑性。有一次同事跑来跟我说 playbook 挂了,我一看 yml 里写的是hosts: production,但他的 inventory 里组名叫prod。这属于手滑型错误,但越蠢的错误越常见。所以当它出现时,第一反应不是去检查 playbook 逻辑,而是用下面 4.2 节的命令先看看-i到底解析出了什么。

4.2 三个验证 inventory 的实用命令

以前排错靠一遍遍跑 playbook,效率极低。后来我习惯先用ansible-inventory这个命令做"静态检查"。

查看解析后的完整结构:

ansible-inventory -i inventory/prod --list

它会输出 JSON 格式的主机分组和变量。如果你只关心分组关系,可以用更直观的图结构输出:

ansible-inventory -i inventory/prod --graph

输出长这样:

@all: |--@prod: | |--@web: | | |--web01 | | |--web02 | |--@db: | | |--db01

这个输出能一眼看出组嵌套关系有没有建对。再来一个是确认当前匹配的设备清单:

ansible -i inventory/prod web --list-hosts

如果这里输出的主机和你预期一致,那基本可以断定 playbook 没问题,问题在后续的连接参数。

4.3 排查思路四步法

遇到-i相关的问题,我一般按固定四步走。

第一步,查解析。先跑ansible-inventory -i 路径 --graph,确认 inventory 本身是否解析成功、分组结构是否正常。这一步能过滤掉 80% 的组名、路径类错误。

第二步,查匹配。跑ansible -i 路径 组名 --list-hosts,这一步把 inventory 解析结果和 playbook 里hosts:字段做对齐,确认两边的组名完全一致。

第三步,查变量。用下面这条命令打印主机归属和变量:

ansible -i inventory/prod web01 -m debug -a 'var=group_names'

这条命令会返回该主机所属的所有组,以及 inventory 里继承的变量。变量覆盖问题在这时候一目了然。比如你发现app_version被 dev 环境覆盖成了旧版本,那就要回看是不是 prod 目录下的 group_vars 文件没有生效,或者路径放错了层级。

第四步,查路径。最后再看一眼ansible.cfg里的配置有没有和命令行-i冲突。比如ansible.cfg里设置了inventory = ./staging/hosts,但你命令行里明明写了-i inventory/prod,按理说命令行的优先级更高,不会出问题;但如果你用--inventory时写错了相对路径,那么 Ansible 就会回落到ansible.cfg,或者压根找不到任何有效 inventory。这种路径问题也常见于 cron 或 CI 脚本中,因为此类环境的工作目录往往不是你本地终端的目录。

4.4 现场排错:一次-i导致的多环境互串事故

最后给你还原一个我实际处理过的真实事故。当时某个项目的 Jenkins 流水线出现了诡异现象:开发环境的部署偶尔把生产 IP 列进了执行清单。全面排查之后,问题定位在了这条命令上:

ansible-playbook -i inventory/ deploy.yml

注意,-i指到了一个目录,而执行时的工作目录处于项目根目录。Ansible 加载了inventory/目录下的所有 inventory 文件,包括dev和prod两个子目录。Ansible 将各来源合并,而 prod 的 inventory 优先级天然高于前面的 dev,于是一部分主机同时出现在两个环境的分组里,playbook 又用了宽泛的hosts: all,直接把两个环境的主机一锅端了。

修复方法很简单:把命令改成显式指定环境子目录:

ansible-playbook -i inventory/dev deploy.yml

这个事故给我的教训是:除非你真的想合并多个 inventory 来源,否则-i一定要精确到具体文件或具体环境目录,绝不能随手指一个上层目录。这在多人协作、自动化流水线里尤为重要,因为底层的机器不关心你在哪个环境,只要有一丝疏漏,后果往往是灾难性的。

5. 关于-i参数的一些实践体会

说了这么多,最后聊几句我在实际使用中的个人经验。

第一条,build inventory 目录结构要趁早规范。项目一上手就按 dev/staging/prod 分目录,每个环境里放hosts.yml和group_vars/。常规踩坑之后,你会感激当初这个决定。别想着"现在就几个主机,直接一个文件写到头",后面扩容的时候改写成本远比你想象的高。

第二条,尽量少用变量去塞 SSH 密码。有些团队为了省事,在 inventory 的 group_vars 里写ansible_ssh_pass,用起来当下是爽,但一旦日志、CI 输出泄露了 inventory 文件,所有机器的密码等于裸奔。我现在的习惯是配合 ssh-agent 或 JumpServer 等堡垒机做认证,inventory 里只写主机地址、端口、用户名,不碰任何凭据。

第三条,Playbook 的 hosts 字段尽量写组名而不是 all。即便有-i做环境隔离,hosts: all依旧是把双刃剑。当你只想部署 web 组机器时,用--limit web是能精确控制,但如果你 lineup 里有两个环境目录,all就会把两个环境的主机都算进来。组名 +-i精确到目录,才是一套可靠的组合。

这些经验都是真金白银换来的。Ansible 看起来是个"照着写就能跑"的工具,但自动化运维恰恰是"所有细节都正确才叫正确"的领域。-i不过是其中一个参数,可它决定了你这套自动化工具,到底连哪一批机器、跑哪一套配置。哪怕只多花两分钟把 inventory 结构想清楚,后面省下的排障时间都不止这个数。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询