Ansible Docker 动态清单

6155 字
31 分钟
Ansible Docker 动态清单

Ansible Docker 动态清单#

[TOC]


概述#

Note

动态清单就是让 Ansible 自己去问 Docker:“你现在有哪些容器在跑?“,把答案自动当成主机列表

环境准备#

组件情况
虚拟机Ubuntu 24.04,IP 192.168.122.211
Docker已安装(/var/run/docker.sock)
Ansiblepipx 安装(/root/.local/bin/)
容器myweb(nginx)、mypy(python
Terminal window
'当前容器状态'
root@m01 ~# docker ps
CONTAINER ID IMAGE NAMES
973a18250fe8 python:alpine3.24 mypy
b009b9d26bd3 nginx:latest myweb
1)初始动态清单
'第一个动态清单'
# 让 Ansible 自动发现这两个正在运行的容器
root@m01 ~# pwd
/root
mkdir -p /root/inventory
root@m01 ~# mkdir -p /root/inventory
root@m01 ~# cat > /root/inventory/docker.yml << 'EOF'
plugin: community.docker.docker_containers
docker_host: unix:///var/run/docker.sock
EOF
root@m01 ~# ansible-inventory -i ~/inventory/docker.yml --list
报错
插件的判断规则是:文件名必须包含 `.docker.` 这个子串
🌰 `docker.yml` 里没有 `.docker.`,插件不认识它,Ansible 就把它当普通 YAML 清单去解析
2)改正
root@m01 ~# mv /root/inventory/docker.yml /root/inventory/docker.docker.yml
root@m01 ~# ansible-inventory -i ~/inventory/docker.docker.yml --list
'注意名字是已经更正过的名字'
Failed to parse inventory with 'auto' plugin.
Failed to import the required Python library (requests)
😭 还是不对 👆

现象:文件名改成 docker.docker.yml 后,插件已经能识别了,但仍然报错:

排错#

requests 库缺失 ✅

Terminal window
'排查过程'
1)怀疑 docker SDK 没装
root@m01 ~# python3 -c "import docker"
ModuleNotFoundError: No module named 'docker'
确实没装,但不是首要问题
'这个python3是系统里面的,不是Ansible中的'
2)怀疑 collection 没装
'Collection(集合)——它是 Ansible 的包管理单位'
root@m01 ~# ansible-galaxy collection list | grep docker
community.docker 5.2.0
# 装了(v5.2.0),排除
3)开详细模式看真实错误
'-vvv' --》 定位到 requests 缺失
oot@m01 ~# ansible-inventory -i /root/inventory/docker.docker.yml --list -vvv
关键行:ModuleNotFoundError: No module named 'requests'
"注意报错里的 Python 路径:"
# /root/.local/share/pipx/venvs/ansible/bin/python

根因:pipx 安装的 Ansible 跑在独立的虚拟环境里(/root/.local/share/pipx/venvs/ansible/

  • community.docker 插件调用 Docker API 时底层依赖 Python 的 requests
    • 但 pipx 的虚拟环境里没有得用 pipx inject 专门给 Ansible 的环境装一份
    • 他没有 requests 库 & docker SDK
Important

关键认知:pipx 安装的工具,它的 Python 虚拟环境是隔离的

  • Ubuntu 24.04 的系统 Python 是”externally managed”,直接 pip install 根本不让装

  • 就算用 --break-system-packages 强行装进系统 Python,pipx 里的 Ansible 也照样看不到

    • 必须用 pipx inject 把依赖注入进 Ansible 自己的虚拟环境
Terminal window
4)修复:
requests docker SDK 注入到 pipx Ansible 虚拟环境
root@m01 ~# pipx inject ansible requests
injected package requests into venv ansible
done! 🌟
root@m01 ~# pipx inject ansible docker
injected package docker into venv ansible
done! 🌟
哈哈...
5)验证:
root@m01 ~# ansible-inventory -i /root/inventory/docker.docker.yml --list
正常输出容器清单
{
"_meta": {
"hostvars": {
"mypy": {
"ansible_connection": {
"__ansible_unsafe": "community.docker.docker_api"
},
....................
},
"myweb": {
"ansible_connection": {
"__ansible_unsafe": "community.docker.docker_api"
},
.....................
}
Tip
  • 光有主机清单,ansible_connection 也没配,还不能执行任何任务

  • 接下来就要引入过滤器连接配置,让这个清单真正可用


过滤器(filters)#

  • 需求: 容器停了的不应该出现在清单里 —> 只想要”正在运行”的

初尝试——直接翻车#

plugin: community.docker.docker_containers
docker_host: unix:///var/run/docker.sock
filters:
- include: "status=running"
Terminal window
root@m01 ~# vim ./inventory/docker.docker.yml
root@m01 ~# ansible-inventory -i ~/inventory/docker.docker.yml --list
Could not evaluate filter condition 'status=running'

过滤器表达式是 Jinja2 语法status=running 有 3 个问题:

错误 ❌原因修正
status没有这个变量;Docker inspect 的 State 键经 slugify 后叫 docker_statedocker_state
=Jinja2 里 = 是赋值,比较用 ====
running裸词在 Jinja2 里是变量名(未定义),字符串必须加引号"running"

三条一起改掉 → 'docker_state == "running"',但往下看——

修正后”通了”——其实没生效#

filters:
- include: 'docker_state == "running"'
Note

外层单引号,里面双引号 "running"

Terminal window
root@m01 ~# ansible-inventory -i ~/inventory/docker.docker.yml --graph
# 树状图方式显示
@all:
|--@ungrouped:
| |--mypy
| |--myweb
# 两个都在,看起来"对了"

问题出在两层

第一层docker_state 不是字符串,是 Docker inspect 返回的 对象

docker_state = {
"Status": "running",
"Running": true,
"Paused": false,
...
}

所以 docker_state == "running" = 拿对象跟字符串比 → 永远 False —> 这条 include 从来没命中过

第二层:既然条件永远不命中,为什么两个容器还在?停掉一个试试:

Terminal window
root@m01 ~# docker stop mypy
root@m01 ~# ansible-inventory -i ~/inventory/docker.docker.yml --graph
@all:
|--@ungrouped:
| |--mypy # ← 停了还在!说明过滤器完全没干活
| |--myweb

→ 引出核心机制 ↓

核心机制:include / exclude + 默认放行#

filters 是一个列表,每一项有且仅有 includeexclude 之一,对每个容器独立求值

Terminal window
对【每个容器】独立执行:
遍历 filters 列表(从上到下):
include 条件 = True 入选,立即停止
exclude 条件 = True 排除,立即停止
⚠️ 走到列表末尾,'没有一条为 True' 入选(默认放行)
关键字角色条件成立时
include白名单入选,不再看后面的 filter
exclude黑名单排除,不再看后面的 filter

include: '"mypy" in docker_name' 为例,两个容器各走各的:

Terminal window
mypy:
filter[0] include: "mypy" in "mypy" True 入选
myweb:
filter[0] include: "mypy" in "myweb" False 继续
走到头了,没有一条 True 默认放行 混进来了!

加上 exclude: true 兜底:

Terminal window
myweb:
filter[0] include: "mypy" in "myweb" False 继续
filter[1] exclude: true True 排除 被拦住!
📌 mypy filter[0] 就入选了,根本走不到这里
Important

必须有一条为 true,否则就会混进来。

  • 所有 filter 条件都是 false → 走到末尾无结论 → 默认放行
  • exclude: true = 永远为 true 的守门员,保证最后一定有一条命中
  • include 只能主动请人进来,拦不住不匹配的

正确写法#

只要运行中的容器——两种方式:

方式写法说明
排除法 ✅ 推荐exclude: 'not docker_state.Running'Running 是==布尔值==,逐个推演:
stopped → Running=falsenot false=true → 命中 → 踢掉 ❌
running → Running=truenot true=false → 不命中 → 留下 ✅
一行搞定
include + 兜底include: 'docker_state.Running'
exclude: true
running → Running=true → 第 1 条命中 → 入选
stopped → Running=false → 第 1 条不命中 → 走到第 2 条 exclude: true 命中 → 踢掉
Warning

单写 include: 'docker_state.Running' 不行——stopped 的 Runningfalse,include 不成立 → ==默认放行==

Terminal window
'验证排除法'
root@m01 ~# docker stop mypy
root@m01 ~# cat > /root/inventory/docker.docker.yml << 'EOF'
plugin: community.docker.docker_containers
docker_host: unix:///var/run/docker.sock
filters:
- exclude: 'not docker_state.Running'
compose:
ansible_connection: community.docker.docker
EOF
root@m01 ~# ansible-inventory -i ~/inventory/docker.docker.yml --graph
@all:
|--@ungrouped:
| |--myweb # ← mypy 被正确排除 ✅

过滤器(filters)字符串比较:用 in 别用 ==

由于 __ansible_unsafe 包装,过滤器中 == 比较字符串不可靠

表达式可用?替代
docker_name == "mypy""mypy" in docker_name
docker_name.startswith("my")"my" in docker_name
docker_state.Status == "running""running" in docker_state.Status ⚠️ ==字符串==
"mypy" in docker_name
docker_state.Running ⚠️ ==布尔值==
Tip

记一个规则:==过滤器(filters)==中字符串判断用 in 别用 == —> ==groups 不受此影响==(走另一条求值路径)

组合条件(and / or)

filters:
- include: 'docker_state.Running and ("nginx" in docker_config.Image)'
- exclude: true
  • and — 两边都满足才留下
  • or — 任意一边满足就留下
  • not — 取反
Terminal window
root@m01 ~# ansible-inventory --graph
@all:
|--@ungrouped:
| |--myweb # ← 只有 nginx + 运行中
容器Runningdocker_config.Image结果
mywebtruenginx:latest
mypytruepython:3.12-alpine
old-nginx (停)falsenginx:1.27

完整示例

filters:
- include: '"my" in docker_name' # 只要名字含 my 的
- exclude: '"test" in docker_name' # 排除含 test 的
- exclude: true # 兜底

连接容器#

发现容器了,试试能不能”连上”——用 ping 模块测试

配置 ansible.cfg#

Terminal window
root@m01 ~# ansible --version | grep 'config file'
config file = /etc/ansible/ansible.cfg
# 现在使用的是系统级配置文件
1)创建一个项目级 `ansible.cfg`,省得每次敲 `-i`
root@m01 ~# cat > /root/ansible.cfg << 'EOF'
[defaults]
inventory = /root/inventory/docker.docker.yml
interpreter_python = auto_silent
[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
EOF

报错 A:sudo: not found#

Terminal window
root@m01 ~# ansible all -m ping
mypy | FAILED! => {
"module_stderr": "/bin/sh: sudo: not found",
"msg": "Module result deserialization failed: ...",
"rc": 127
}
myweb | FAILED! => {
"module_stderr": "/bin/sh: 1: sudo: not found",
...
}

所有容器都失败 ❌

为什么? ansible.cfg 里配了 become = true(开启 sudo 提权),Ansible 每次执行命令前都会先敲 sudo

但 Docker 容器里根本没有 sudo 这个命令——追求体积小的镜像(nginx、alpine)不可能装 sudo

⚠️ 更重要的是:容器里的进程本身就是 root,根本不需要提权


管理虚拟机 vs 管理容器,配置文件是不一样的:

配置项虚拟机容器
连接方式SSHDocker API
默认用户test(普通用户)root
需要 sudo?
privilege_escalation需要不要配!

✅ 修正

Terminal window
root@m01 ~# cat > /root/ansible.cfg << 'EOF'
[defaults]
inventory = /root/inventory/docker.docker.yml
interpreter_python = auto_silent
EOF
🐎 去掉整个 `[privilege_escalation]` 区块

报错 B:/usr/bin/python3: not found#

修正后再试:

Terminal window
root@m01 ~# ansible all -m ping
myweb | FAILED! => {
"module_stderr": "/bin/sh: 1: /usr/bin/python3: not found\n",
"rc": 127
}
mypy | SUCCESS => {
"ping": "pong"
}
python 容器通了,nginx 容器还是失败,但'报错内容变了'
——> 不是 `sudo` 问题了,是找不到 Python

为什么❓

  • ping 模块本质上是一个 Python 脚本
    • Ansible 把它上传到目标主机,然后找一个 Python 解释器来执行
Terminal window
ansible ping 模块的执行过程:
1. ping 模块(Python 代码)传进容器
2. 在容器里找 Python 解释器(由 interpreter_python 配置决定)
3. Python 执行这段代码
4. 返回结果
  • mypy(python 镜像)→ 自带 Python → ✅
  • myweb(nginx 镜像)→ 没有 Python → ❌
Note

Docker 连接插件(community.docker.docker)只替换了传输层(“怎么把命令送进容器”),并没有替换执行层(“用什么来跑模块”)

Terminal window
SSH 连接: ansible --网络--> SSH服务 --> 容器(需要 Python)
Docker 连接: ansible --docker exec--> 容器(同样需要 Python)
唯一的例外:'raw 模块'

✅ 解决方案:raw 模块

Tip

raw 模块不传 Python 代码,直接跑 shell 命令——不需要 Python:

Terminal window
root@m01 ~# ansible all -m raw -a 'echo hello'
mypy | CHANGED | rc=0 >>
hello
myweb | CHANGED | rc=0 >>
hello
==============================================
root@m01 ~# ansible all -m raw -a 'whoami'
mypy | CHANGED | rc=0 >>
root
myweb | CHANGED | rc=0 >>
root
==============================================
root@m01 ~# ansible myweb -m raw -a 'nginx -v'
myweb | CHANGED | rc=0 >>
nginx version: nginx/1.31.1

适用场景:操作 nginx、redis、mysql 等镜像——这些镜像追求体积小,不带 Python

✅ 日常管理它们就靠 raw 模块

汇总

模块需要 Python?能用于 nginx 容器?
raw❌ 不需要
ping✅ 需要
command / shell✅ 需要
setup(收集信息)✅ 需要

Terminal window
'在nginx容器中安装python'
1)进入判断系统
root@m01 ~# docker exec -it myweb /bin/bash
root@ee5d00b5904c:/# ls /etc/ | grep -E 'apt|yum'
apt
root@ee5d00b5904c:/# cat /etc/apt/sources.list.d/debian.sources
Types: deb
# http://snapshot.debian.org/archive/debian/20260610T000000Z
URIs: http://deb.debian.org/debian 📌
............
Types: deb
# http://snapshot.debian.org/archive/debian-security/20260610T000000Z
URIs: http://deb.debian.org/debian-security 📌
2)替换源
root@ee5d00b5904c:/# sed -i 's#deb.debian.org#mirrors.aliyun.com#g' /etc/apt/sources.list.d/debian.sources
root@ee5d00b5904c:/# cat /etc/apt/sources.list.d/debian.sources
Types: deb
# http://snapshot.debian.org/archive/debian/20260610T000000Z
URIs: http://mirrors.aliyun.com/debian
Suites: trixie trixie-updates
Components: main
Signed-By: /usr/share/keyrings/debian-archive-keyring.pgp
Types: deb
# http://snapshot.debian.org/archive/debian-security/20260610T000000Z
URIs: http://mirrors.aliyun.com/debian-security
Suites: trixie-security
Components: main
Signed-By: /usr/share/keyrings/debian-archive-keyring.pgp
3)下载安装
root@ee5d00b5904c:/# apt update && apt -y install python3
root@ee5d00b5904c:/# python3 -V
Python 3.13.5
root@ee5d00b5904c:/# exit
exit
4)退出测试
root@m01 ~# ansible all -m ping
ansible.cfg中配置了interpreter_python=auto_silent
# 可以自动识别受控节点的python环境
myweb | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3.13"
},
"changed": false,
"ping": "pong"
}
mypy | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/local/bin/python3.12"
},
"changed": false,
"ping": "pong"
}

动态分组(groups)#

==需求==:容器多了以后,需要分组管理——“web 容器归一组,python 容器归一组”:

Terminal window
ansible web_servers -m raw -a 'nginx -s reload' # 只重载 web 组
ansible py_apps -m ping # 只 ping python 组

⚠️ 关键认知

  • filters(过滤器) 和 groups 共享同一套 full_facts,变量完全一样

    • 区别在于角色:filters 控制谁入选,groups 控制入选后怎么分队
  • --host 输出只是 inventory 视图,groups 能用的变量远多于 --host

方法一:按名称 / 镜像分组#

full_facts 中已经包含了所有 inspect 数据,直接引用即可:

/root/inventory/docker.docker.yml
plugin: community.docker.docker_containers
docker_host: unix:///var/run/docker.sock
filters:
- exclude: 'not docker_state.Running'
compose:
ansible_connection: community.docker.docker
# 显式指定连接方式
groups:
web_servers: 'docker_name == "myweb"'
py_apps: 'docker_name == "mypy"'
nginx_hosts: '"nginx" in docker_config.Image' # 镜像中in(包含)nginx
# 所有基于 nginx镜像 创建的容器分一组
Note
  • in子串包含,不是完全相等,“my” 能同时命中 mypy 和 myweb
  • == — ==完全相等==,但 filter 中不可靠(groups 中没问题)

groups(动态分组)要求没有那么严格 —> ==in 都可以用

== 在 filter 中不可靠

Terminal window
root@m01 ~# docker run -d --name web01 nginx:latest
'再创建一个web容器'
root@m01 ~# ansible-inventory --graph
@all:
|--@ungrouped:
|--@nginx_hosts:
| |--web01
| |--myweb
|--@web_servers:
| |--myweb
|--@py_apps:
| |--mypy

方法二:按标签分组#

Docker 标签存在 docker_config.Labels 中,给容器打标签并分组:

Terminal window
'清理环境'
root@m01 ~# docker rm -f $(docker ps -aq)
1)创建容器 & 带标签
⚠️ Docker 不支持修改已有容器的标签,要删除标签只能 `docker rm -f` 后重建
root@m01 ~# docker run -d --name myweb --label group=web_servers nginx:latest
root@m01 ~# docker run -d --name test_web --label group=test nginx:latest
root@m01 ~# docker run -d --name mypy --label group=py_apps python:3.12-alpine tail -f /etc/hosts
# 得阻塞住进程
2)查看标签
root@m01 ~# docker inspect myweb --format '{{json .Config.Labels}}'
{"group":"web_servers","maintainer":"NGINX Docker Maintainers <docker-maint@nginx.com>"}
root@m01 ~# docker inspect test_web --format '{{json .Config.Labels}}'
{"group":"test","maintainer":"NGINX Docker Maintainers <docker-maint@nginx.com>"}
root@m01 ~# docker inspect mypy --format '{{json .Config.Labels}}'
{"group":"py_apps"}
groups:
py_apps: 'docker_config.Labels.group == "py_apps"'
test_group: 'docker_config.Labels.group == "test"'
# ==(完全相等)
nginx_hosts: '"nginx" in docker_config.Image'
# 镜像中in(包含)nginx
Warning
  • groups(动态分组) 中直接 .group 不会报错
    • 宽松求值,取不到返回 undefined,==不炸== ✅
  • filter(过滤器) 必须.get("group", ""),不能直接 .group
    • 没有该标签的容器 Labels 是空 dict {}直接取属性会报错

💡 两者都可以用 .get() 👇 这种方式 —> 更显式

groups:
test_group: 'docker_config.Labels.get("group", "") == "test"'
Terminal window
3)测试运行
root@m01 ~# ansible-inventory --graph
@all:
|--@ungrouped:
|--@test_group:
| |--test_web 🔄
|--@nginx_hosts:
| |--test_web 🔄
| |--myweb
|--@py_apps:
| |--mypy
一个容器可以同时属于多个组 --> 'test_web'
4)创建一个'不带标签'
# 并再次验证
root@m01 ~# docker run -d --name web01 nginx:latest
root@m01 ~# ansible-inventory --graph
@all:
|--@ungrouped:
|--@nginx_hosts:
| |--web01
| |--test_web 🔄
| |--myweb
|--@test_group:
| |--test_web 🔄
|--@py_apps:
| |--mypy
5)停止并再次验证
root@m01 ~# docker stop -t 0 web01
web01
root@m01 ~# ansible-inventory --graph
@all:
|--@ungrouped:
|--@test_group:
| |--test_web 🔄
|--@nginx_hosts:
| |--test_web 🔄
| |--myweb
|--@py_apps:
| |--mypy
'没有web01了'

⭐compose 的真正用途#

Terminal window
root@m01 ~# ansible-inventory --host test_web
{
"ansible_connection": "community.docker.docker_api",
"ansible_docker_api_version": "auto",
"ansible_docker_docker_host": "unix:///var/run/docker.sock",
"ansible_docker_timeout": 60,
"ansible_docker_tls": false,
"ansible_docker_use_ssh_client": false,
"ansible_docker_validate_certs": false,
"ansible_host": "test_web",
"docker_name": "test_web", 📌 # 容器名
"docker_short_id": "0e4c6edb0722d" 📌 # 容器id
} 🤡 # 现在关于docker的变量只有这两个
Tip
  • 简化别名:把 docker_config.Image 映射为 my_img,表达式更短

  • 计算新变量:通过 Jinja2 模板组合多个字段

  • 写入 inventorycompose 变量会出现在 --host 输出中,供 playbook 使用

compose:
ansible_connection: community.docker.docker # 指定连接方式
my_img: docker_config.Image # 简化变量
is_test: 'docker_config.Labels.group == "test"' # 计算布尔标记
my_group: docker_config.Labels.group # 暴露到 --host
Terminal window
root@m01 ~# vim inventory/docker.docker.yml
root@m01 ~# ansible-inventory --host test_web
{
................................
"ansible_host": "test_web",
"docker_name": "test_web",
"docker_short_id": "0e4c6edb0722d",
"my_img": "nginx:latest" 😍
"is_test": true, 😍
"my_group": "test", 😍
}

✨变量速查#

变量filtersgroups--host类型备注
docker_name字符串内置
docker_short_id字符串内置
docker_state.Running==布尔值==true / false
docker_state.Status✅(用 in字符串"running" / "exited"
docker_config.Image 🥳字符串"nginx:latest"
docker_config.Labels.<key>✅(必须 .get字符串没该标签的容器 Labels = {}
docker_image 🤡字符串 (SHA256)⚠️ 是哈希,镜像名在 docker_config.Image
Tip

字符串判断差异

  • filter== 不可靠,用 "xxx" in var(双引号 + in⚠️ 外面单引号包裹
  • group==in 都可以 ✅

groups 工作流程#

Terminal window
1. Docker 插件拿到所有容器
2. filters full_facts 筛一轮 决定哪些容器"入选"
3. compose 把计算结果注入 full_facts
4. groups full_facts 逐个判断 表达式为 true 就加入对应组
5. 一个容器可以同时属于多个组

最终配置汇总

Terminal window
'项目目录最终两个文件:'
root@m01 ~# tree ./
./
├── ansible.cfg # 项目级配置
└── inventory
└── docker.docker.yml # 动态清单
2 directories, 2 files

ansible.cfg

[defaults]
inventory = /root/inventory/docker.docker.yml
interpreter_python = auto_silent
# 注意:没有 [privilege_escalation]!
# 容器里是 root,没有 sudo,不需要提权

docker.docker.yml

plugin: community.docker.docker_containers
docker_host: unix:///var/run/docker.sock
filters:
- exclude: 'not docker_state.Running'
compose:
ansible_connection: community.docker.docker # 指定连接方式
my_img: docker_config.Image # 简化变量
is_test: 'docker_config.Labels.group == "test"' # 计算布尔标记
my_group: docker_config.Labels.group # 暴露到 --host
groups:
py_apps: 'my_group == "py_apps"'
test_group: 'my_group == "test"'
# ==(完全相等)
nginx_hosts: '"nginx" in my_img'
# 镜像中in(包含)nginx
Terminal window
root@m01 ~# vim ./inventory/docker.docker.yml
root@m01 ~# ansible-inventory --graph
@all:
|--@ungrouped:
|--@test_group:
| |--test_web
|--@nginx_hosts:
| |--test_web
| |--myweb
|--@py_apps:
| |--mypy
Tip

这套写法的优点

  • compose 把又长又深的路径(docker_config.Labels.groupdocker_config.Image)映射成短别名(my_groupmy_img
    • groups 表达式立刻干净了 & 好读得多
  • 而且 compose 变量会写进 inventory,--host 也能看到,调试和 playbook 引用都方便

📌 一句话filters 控制”谁入选”,compose 给入选的容器起别名 / 算新值,groups 用这些简洁变量分队

一个容器属于多个组完全没问题

Podman ⬌ Docker 对照#

配置项PodmanDocker
插件containers.podman.podman_containerscommunity.docker.docker_containers
连接方式connection_plugin: containers.podman.podmancompose: ansible_connection: community.docker.docker
控制启停include_stopped: falsefilters: - exclude: 'not docker_state.Running'
镜像变量podman_image(镜像名)docker_config.Image(镜像名,可直接用);docker_image 是 SHA256
状态变量podman_statusdocker_state(对象,取 .Status.Running
标签变量podman_labelsdocker_config.Labels.<key>(直接可用)

主机模式(Host Patterns)#

Note

有了分组之后,下一步就是精准定位——“想对谁执行就对谁执行”

Ansible 提供了一套主机模式语法,支持单个主机、多个主机、组、排除、交集、切片等操作

模式速查表#

描述模式目标
所有主机all*清单中的全部主机
单个主机host1指定主机名
多台主机host1:host2host1,host2冒号或逗号分隔
一个组webservers该组所有成员
多个组(==并集==)group1:group2两组所有成员合并
排除主机group:!host从组中剔除某台主机
排除组group1:!group2从 group1 中踢掉 group2 所有成员
组的交集(==交集==)group1:&group2同时属于两个组的主机
组位置(切片)group[0]group[0:2]按索引取组内主机
正==则==匹配~pattern匹配主机名符合正则的
Warning

模式中包含特殊字符(!&*~)时,shell 可能会抢着解释,必须用引号包裹

Terminal window
root@m01 ~# ansible-inventory --graph
@all:
|--@ungrouped:
|--@test_group:
| |--test_web
|--@nginx_hosts:
| |--test_web
| |--myweb
|--@py_apps:
| |--mypy
root@m01 ~# ansible nginx_hosts:!test_web -m raw -a 'echo hi'
'冒号 :! 感叹号'
-bash: !test_web: event not found
不加引号 shell ! 当历史展开
root@m01 ~# ansible 'nginx_hosts:!test_web' -m raw -a 'echo hi'
# nginx_hosts组中排除test_web --> 只剩myweb
myweb | CHANGED | rc=0 >>
hi
加引号

基础定位:all / 单主机 / 多主机#

Terminal window
'环境确认 — 当前有 3 台运行中的容器'
[root@m01 ~]# ansible-inventory --graph
@all:
|--@ungrouped:
|--@test_group:
| |--test_web 🔄
|--@nginx_hosts:
| |--test_web 🔄
# 同时属于 test_group 和 nginx_hosts
| |--myweb
|--@py_apps:
| |--mypy

① 所有主机 all

Terminal window
[root@m01 ~]# ansible all -m raw -a 'echo hello'
test_web | CHANGED | rc=0 >>
hello
mypy | CHANGED | rc=0 >>
hello
myweb | CHANGED | rc=0 >>
hello

② 单台主机

Terminal window
root@m01 ~# ansible mypy -m raw -a 'hostname'
mypy | CHANGED | rc=0 >>
d01c8848c7ea # ← 容器 ID(hostname 在容器里就是容器 ID)

③ 多台主机 — 冒号 : 或逗号 ,

Terminal window
root@m01 ~# ansible 'myweb:test_web' -m raw -a 'hostname'
⚠️ '别忘记加单引号'
myweb | CHANGED | rc=0 >>
3c6b015c1a82
test_web | CHANGED | rc=0 >>
0e4c6edb0722
root@m01 ~# ansible 'myweb,test_web' -m raw -a 'hostname'
'逗号效果完全一样'
myweb | CHANGED | rc=0 >>
3c6b015c1a82
test_web | CHANGED | rc=0 >>
0e4c6edb0722
Tip

,: 效果相同,: 还能跟 !& 组合建议统一用冒号

组定位:单组 / 并集#

④ 针对某个组

Terminal window
root@m01 ~# ansible nginx_hosts -m raw -a 'echo nginx组'
test_web | CHANGED | rc=0 >>
nginx组
myweb | CHANGED | rc=0 >>
nginx组

⑤ 多个组的并集

Terminal window
root@m01 ~# ansible 'nginx_hosts:py_apps' -m raw -a 'echo 并集'
myweb | CHANGED | rc=0 >>
并集
test_web | CHANGED | rc=0 >>
并集
mypy | CHANGED | rc=0 >>
并集
'3 台都在:nginx 组 2 台 + python 组 1 台'

排除:! 操作符#

从组中踢掉特定主机或组:

Terminal window
'⑥ 从组中排除单台主机'
root@m01 ~# ansible 'nginx_hosts:!test_web' -m raw -a 'echo 排除test_web后'
myweb | CHANGED | rc=0 >>
排除test_web后
# ✅ test_web 没了,只剩 myweb
=============================
'踢掉test_group'
root@m01 ~# ansible 'nginx_hosts:!test_group' -m raw -a 'echo 踢掉test_group'
myweb | CHANGED | rc=0 >>
踢掉test_group
# test_group只有test_web一台主机
步骤集合
初始 nginx_hosts{myweb, test_web}
执行 :!test_web踢掉 test_web
最终{myweb}

交集:& 操作符#

取两个组的公共成员:

Terminal window
'⑦ 组的交集'
root@m01 ~# ansible 'nginx_hosts:&test_group' -m raw -a 'echo 交集'
test_web | CHANGED | rc=0 >>
交集
# ✅ 只有 test_web:它同时属于 nginx_hosts 和 test_group
步骤集合
nginx_hosts{myweb, test_web}
test_group{test_web}
交集{test_web} ✅

组合运算:先交集再排除#

!& 可以组合,按从左到右顺序执行:

Terminal window
'⑧ 组合 — 交集后排除'
root@m01 ~# docker run -d --name test02 --label group=test nginx:latest
root@m01 ~# ansible-inventory --graph
@all:
|--@ungrouped:
|--@test_group:
| |--test02
| |--test_web
|--@nginx_hosts:
| |--test02
| |--test_web
| |--myweb
# 场景:取 nginx_hosts ∩ test_group 的交集, 再排除test_web
root@m01 ~# ansible 'nginx_hosts:&test_group:!test_web' -m raw -a 'echo 交集并排除后的结果'
test02 | CHANGED | rc=0 >>
交集并排除后的结果

组位置 / 切片:[n] 语法#

索引从 0 开始 —> 单个索引 [n] 与 Python 一致,但切片有一个关键区别

Warning

Ansible 切片 [start:stop]stop 是包含的(inclusive),不是 Python 的排除(exclusive)!

Python list[0:1]Ansible group[0:1]
语义[0, 1) 不含 stop[0, 1] 含 stop
结果1 个元素(索引 0)2 个元素(索引 0 和 1)
Terminal window
'nginx_hosts 组共 3 台,按顺序:'
[0] test02
[1] test_web
[2] myweb
'⑨ 单个索引 — 与 Python 完全一致'
[root@m01 ~]# ansible 'nginx_hosts[0]' -m raw -a 'echo 索引0'
test02 | CHANGED | rc=0 >>
索引0
[root@m01 ~]# ansible 'nginx_hosts[1]' -m raw -a 'echo 索引1'
test_web | CHANGED | rc=0 >>
索引1
[root@m01 ~]# ansible 'nginx_hosts[-1]' -m raw -a 'echo 倒数第一'
myweb | CHANGED | rc=0 >>
倒数第一
'⑩ 切片 — ⚠️ stop 也是包含的(两端都算!)'
# 用实验来证明,对照 Python 的预期:
[root@m01 ~]# ansible 'nginx_hosts[0:0]' -m raw -a 'echo 0:0'
test02 | CHANGED | rc=0 >>
0:0
# ← 1 台!Python [0:0] 是空的,Ansible 两端都包含 → {索引0}
[root@m01 ~]# ansible 'nginx_hosts[0:1]' -m raw -a 'echo 0:1'
test02 | CHANGED | rc=0 >>
0:1
test_web | CHANGED | rc=0 >>
0:1
# ← 2 台!Python [0:1] 只取索引0,Ansible stop=1 也包含 → {0,1}
[root@m01 ~]# ansible 'nginx_hosts[0:2]' -m raw -a 'echo 0:2'
test02 | CHANGED | rc=0 >>
0:2
test_web | CHANGED | rc=0 >>
0:2
myweb | CHANGED | rc=0 >>
0:2
3 台全部!Python [0:2] 只取 0,1,Ansible stop=2 包含 {0,1,2}
[root@m01 ~]# ansible 'nginx_hosts[1:2]' -m raw -a 'echo 1:2'
test_web | CHANGED | rc=0 >>
1:2
myweb | CHANGED | rc=0 >>
1:2
# ← 2 台!Python [1:2] 只取索引1,Ansible → {1,2}

--limit:无需改命令,临时缩小范围#

模式可以直接写在命令行上,也可以通过 --limit 叠加过滤——不需要修改模式本身

Terminal window
'⑪ --limit 排除主机'
root@m01 ~# ansible all -m raw -a 'echo hi' --limit 'all:!test_web'
myweb | CHANGED | rc=0 >>
hi
test02 | CHANGED | rc=0 >>
hi
mypy | CHANGED | rc=0 >>
hi
# test_web 被 limit 排除 ✅
'⑫ --limit 指定组'
[root@m01 ~]# ansible all -m raw -a 'echo mixed' --limit 'py_apps:nginx_hosts'
test02 | CHANGED | rc=0 >>
mixed
test_web | CHANGED | rc=0 >>
mixed
myweb | CHANGED | rc=0 >>
mixed
mypy | CHANGED | rc=0 >>
mixed
'⑬ --limit 正则排除'
📌 `~` 前缀 = 正则匹配
📌 `~` 还可以和排除 `!` 组合:
root@m01 ~# ansible all -m raw -a 'echo 排除以test开头的组' --limit 'all:!~test.*'
⚠️ 关键发现:`~` 隐式锚定开头(自带 `^` `~test` == `~^test`
myweb | CHANGED | rc=0 >>
排除以test开头的组
mypy | CHANGED | rc=0 >>
排除以test开头的组
# ✅ test 开头的主机全被排除(test_web、test02 都不见了)
Tip

其实不用 --limit 更快~ 可以直接写在命令行模式里,一行搞定:

📌 --limit 场景:命令行模式已经写好了不想改,临时叠加一个 --limit 做二次过滤

写法效果
'~test.*'匹配主机名符合正则的
'~.*py$'匹配以 py 结尾的主机
'all:!~test.*'排除匹配正则的主机
'组名:!~test.*'组内排除
Terminal window
[root@m01 ~]# ansible '~test.*' -m raw -a 'echo 匹配正则'
test02 | CHANGED | rc=0 >>
匹配正则
test_web | CHANGED | rc=0 >>
匹配正则
# ← 只有 test 开头的主机被命中 ✅
[root@m01 ~]# ansible 'all:!~test.*' -m raw -a 'echo 排除test开头的'
⚠️ `~` 隐式锚定开头(自带 `^`
myweb | CHANGED | rc=0 >>
排除test开头的
mypy | CHANGED | rc=0 >>
排除test开头的

从文件读取主机列表:--limit @文件#

Terminal window
'⑭ 把目标主机写进文件'
[root@m01 ~]# cat > /root/host.txt << 'EOF'
myweb
mypy
EOF
[root@m01 ~]# ansible all -m raw -a 'echo 来自文件' --limit @/root/host.txt
myweb | CHANGED | rc=0 >>
来自文件
mypy | CHANGED | rc=0 >>
来自文件
# ← test_web、test02 不在文件中,不执行 ✅

小结#

核心规则速记

符号含义类比
:并集(分隔)集合 ∪
:!排除集合 −
:&交集集合 ∩
[n]取单个,索引第 n 台Python list[n]
[m:n] ⚠️切片,两端都包含Python list[m:n+1]
--limit叠加过滤(不影响命令行里的模式)二次筛选
Tip

实战建议

  • 日常操作:ansible <组名> -m raw -a '命令' 最常用
  • 调试单台:ansible <主机名> -m raw -a '命令'
  • 排除故障机:用 '组名:!故障机'
  • 快速过滤:'~test.*' 直接正则匹配主机名,秒定位一类主机
  • 批量排除:'all:!~test.*' 直接写在模式里即可,不用 --limit
  • 批量读取:把主机名写进文件,--limit @文件
  • ⚠️ 切片取 N 台:group[0:N-1],stop 是包含的!

命令速查#

Terminal window
# -------- 清单 --------
ansible-inventory --graph # 树状图
ansible-inventory --list # JSON 详情
ansible-inventory --host myweb # 某个主机的所有变量
ansible-inventory --list -vvv # 详细模式排错
# -------- 执行 --------
ansible all -m ping # ping(需要 Python)
ansible all -m raw -a 'whoami' # raw(不需要 Python)
ansible all -m debug -a "var=ansible_connection" # 查看连接方式
ansible web_servers -m raw -a 'nginx -v' # 针对某个组执行
# -------- 主机模式 --------
ansible 'group:!host' -m raw -a 'cmd' # 从组中排除某台主机
ansible 'g1:&g2' -m raw -a 'cmd' # 两个组的交集
ansible 'group[0]' -m raw -a 'cmd' # 组内第一台
ansible 'group[0:1]' -m raw -a 'cmd' # 前两台(⚠️ stop 包含,[0:1]=索引0和1)
ansible '~test.*' -m raw -a 'cmd' # 正则过滤(直接模式,更快 ⚡)
ansible 'all:!~test.*' -m raw -a 'cmd' # 正则排除(直接模式,不用 --limit)
ansible all -m raw -a 'cmd' --limit @file # 从文件读取主机列表
ansible all -m raw -a 'cmd' --limit 'all:!~pattern.*' # --limit 正则排除
# -------- 环境 --------
ansible --version # 版本 + 当前使用的配置文件
ansible-galaxy collection list | grep docker # 确认 Docker 插件已安装

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Ansible Docker 动态清单
https://www.kpyun.fun/posts/automation/ansible/ansible06/
作者
久棹
发布于
2026-06-15
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
久棹
只要胆子大,天天寒暑假!
公告
欢迎来到久棹的技术小站!本站专注 Linux 运维学习笔记分享,如有问题欢迎交流探讨 🎉
分类
标签
站点统计
文章
98
分类
11
标签
203
总字数
244,453
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.13.5
文章许可
CC BY-NC-SA 4.0

文章目录