Ansible 无原生动态 blocks,但可通过 facts+when+loop+幂等模块实现按实际硬盘数动态格式化:先识别裸盘,再循环执行分区/格式化/挂载,最后加白名单增强安全性。

Ansible 本身没有 native 的 “dynamic blocks” 机制——这是个常见误解。YAML 不支持运行时动态生成 block 结构,Ansible 的 playbooks 是静态解析的。但你可以通过 事实收集(facts)+ 条件判断(when)+ 循环(loop/with_dict/loop: "{{ ... }}")+ 模块幂等性,实现逻辑上等效的“按实际硬盘数量动态执行格式化”效果。
关键不在于“动态写 block”,而在于:让同一段任务,对每块符合条件的磁盘,智能跳过或执行。
下面分三步说清楚怎么做:
✅ 第一步:准确识别“待格式化的裸盘”
利用 ansible_facts 中的 ansible_devices 自动发现所有块设备,并筛选出无分区、未挂载、未被 LVM 占用的磁盘:
- name: Collect disk facts
setup:
gather_subset: hardware
- name: Find candidate raw disks
set_fact:
raw_disks: >-
{{
ansible_devices.keys()
| map('regex_replace', '^nvme\d+n\d+$', '') # 忽略 NVMe 命名干扰(如 nvme0n1 → nvme0)
| list
| difference(['loop', 'ram', 'sr']) # 排除伪设备
| map('regex_search', '^[shv]d[a-z]$|^[xv]d[a-z]$|^[a-z]{2,3}$') # 匹配 sda/xvdb/vdb 等主流盘符
| select('defined')
| map(attribute='0')
| list
| unique
}}
vars:
ansible_devices: "{{ ansible_facts.ansible_devices }}"然后逐个检查每块盘是否满足“可格式化”条件(无分区 + 未挂载 + 非 LVM PV):
- name: Check each candidate disk status
shell: |
lsblk -p -o NAME,FSTYPE,MOUNTPOINT,TYPE /dev/{{ item }} 2>/dev/null |
awk '$4=="disk" && $2=="" && $3=="" {print "raw"} $4=="part" {exit 1}'
args:
executable: /bin/bash
register: disk_check
loop: "{{ raw_disks }}"
ignore_errors: true
- name: Build list of truly raw disks
set_fact:
format_targets: >-
{{
format_targets | default([])
+ ([item.item] if item.stdout == 'raw' else [])
}}
loop: "{{ disk_check.results }}"? 这样得到的
format_targets就是真实存在的、空闲的、可安全分区格式化的磁盘列表,比如['sdb', 'sdc']。
✅ 第二步:对每个目标盘执行“分区 → 格式化 → 挂载”原子链
用 loop 驱动整个流程,每个盘独立走完三步,互不干扰:
- name: Partition raw disk
parted:
device: "/dev/{{ item }}"
number: 1
state: present
part_end: 100%
loop: "{{ format_targets }}"
when: >
(ansible_facts.ansible_devices[item] is defined)
and (ansible_facts.ansible_devices[item].partitions | default({}) | length == 0)
- name: Format partition as xfs
filesystem:
fstype: xfs
dev: "/dev/{{ item }}1"
loop: "{{ format_targets }}"
when: >
(ansible_facts.ansible_devices[item] is defined)
and (ansible_facts.ansible_devices[item].partitions | default({}) | length == 0)
and (ansible_facts.ansible_devices[item].partitions | default({}) | length == 0) # 冗余防错,实际可省
- name: Mount formatted disk to /data/{{ item }}
mount:
path: "/data/{{ item }}"
src: "/dev/{{ item }}1"
fstype: xfs
state: mounted
opts: defaults,noatime
loop: "{{ format_targets }}"
when: >
(ansible_facts.ansible_devices[item] is defined)
and (ansible_facts.ansible_devices[item].partitions | default({}) | length == 0)⚠️ 注意:
parted和filesystem模块都具备幂等性。即使重复运行,只要/dev/sdb1已存在且已格式化,它们会跳过操作,不会报错。
✅ 第三步:增强健壮性 —— 跳过系统盘、保留已有数据盘
加一层白名单/黑名单逻辑,避免误操作:
- name: Define safe-to-format disks
set_fact:
safe_format_list: >-
{{
format_targets
| difference(['sda', 'vda', 'xvda', 'nvme0n1']) # 明确排除启动盘
| difference(boot_disk | default([])) # 或从 ansible_boot_device 推导
}}
- name: Use safe_format_list in all subsequent tasks
debug:
msg: "Will format: {{ safe_format_list }}"你还可以结合 lsblk -f 或 blkid 输出,过滤掉已有 UUID 或文件系统的盘,进一步规避风险。
不需要写多个 playbook,也不需要 Python 脚本预生成 YAML。
靠 facts + loop + when + 幂等模块,就能让 Ansible 在每台机器上“看菜下饭”:
- 目标机有 2 块新盘?就跑 2 次格式化;
- 有 5 块?就跑 5 次;
- 一块都没有?所有任务自动跳过,零副作用。
这才是生产环境真正可靠、可审计、可复现的动态适配方式。

















