LabHub
学习 学习路径 课程

Ansible 实战

loop 与 when — 增加的是数据,不是任务

在 LabHub 中继续学习

一句话总结

不要复制同一个 task 生成十份,而应只保留一个 task,把数据写成十行

循环图: 数据写成十行 · 同时使用 loop 与 when 时,条件会对每个循环项分别求值。 · 重试只会延后失败,不会消除失败。 · 第一,循环内的幂等性。

为什么需要它

如果通过复制粘贴编写创建三个用户的 playbook,增加第四个用户时就会再复制一个 task。只要其中一个漏掉权限参数,该用户的配置就会不同,而代码看起来几乎完全一致,很难凭肉眼发现。

loop 消除了这种结构性风险。task 只有一个,遗漏参数的位置也只有一个;数据像表格一样排列,缺失字段一眼可见。

如何工作

loop 接收列表,把每个元素依次放入 item 后重复执行 task。列表元素若是字典,可用 item.name 访问字段。

when 表示条件。同时使用 loopwhen 时,条件会对每个循环项分别求值。 因此,只处理满足条件的项,形成“条件循环”。

有几种组合需要留意。

工具 用途 陷阱
loop_control.label 指定日志中的简短名称 未设置时,整个字典会暴露在日志中
until / retries / delay 重试直到条件为真 untilloop 一起使用较复杂
product 过滤器 两个列表的所有组合 组合数量按乘法增长
fileglob 查询 匹配模式的文件列表 不保证顺序

loop_control.label 也关系到安全。字典包含密码时,默认日志会原样输出。指定 label 或使用 no_log 更安全。

until 表达“等待直到成功”,常用于等待服务启动。但重试只会延后失败,不会消除失败。 设计次数与间隔时,必须同时判断“超过这个时间仍未成功,就是真正的问题”。

在实际项目中

第一,循环内的幂等性。 被循环的 task 也必须对每个项幂等。循环十次,非幂等造成的损害也会放大十倍。

第二,组合爆炸。 环境 3 × 组件 2 = 6 尚可,再乘以 5 个区域就变为 30。组合循环虽然方便,但执行时间与日志长度也会相乘。

第三,不依赖顺序。 文件模式查询不保证结果顺序。顺序重要时,应显式排序或使用固定列表。

loop 的形式与代价

# 목록을 그대로
- name: 패키지 설치
  apt: {name: "{{ item }}", state: present}
  loop: [git, curl, jq]           # ❌ 태스크가 세 번 돈다

# 모듈이 목록을 받으면 한 번에
- name: 패키지 설치
  apt: {name: [git, curl, jq], state: present}   # ✅ apt 한 번

模块支持列表时,不要使用 loop。 aptyumpip 可以一次处理列表;用 loop 就会为每个软件包重新调用 apt,一百个包就是一百次。

遍历复杂数据时,用 loop_control 让输出更易读。

- name: 사용자 만들기
  user: {name: "{{ item.name }}", groups: "{{ item.groups }}"}
  loop: "{{ users }}"
  loop_control:
    label: "{{ item.name }}"      # 로그에 딕셔너리 전체 대신 이름만
    pause: 1                       # 항목 사이 1초 — API 속도 제한이 있을 때

没有 label,日志会被整个字典填满;若包含 secret,还会原样泄露,因此这也是安全问题,通常应配合 no_log: true

when 的求值时机

when 会对每个项分别求值,与 loop 一起使用时按项过滤。

- name: 프로덕션 호스트에만
  copy: {src: prod.conf, dest: /etc/app.conf}
  when: env == 'prod'              # 호스트마다 평가

when 内不要使用 {{ }} 它本身已经处于 Jinja2 表达式上下文,再包一层会出现警告,或被当成字符串求值。

when: env == 'prod'                # ✅
when: "{{ env == 'prod' }}"        # ❌ 되기는 하지만 경고

处理未定义变量时,先写 is defined。它像 Python 一样短路求值,后续条件因此安全。

when: extra_port is defined and extra_port | int > 1024

处理失败的三种方式

- command: /opt/check.sh
  register: result
  failed_when: result.rc not in [0, 2]    # 2도 성공으로 본다
  changed_when: false                      # 상태를 안 바꾸는 조회
  ignore_errors: true                      # 실패해도 계속 (최후의 수단)

ignore_errors 很容易被滥用。忽略失败后,后续 task 会在错误前提上继续运行。 通常更合适的做法是用 failed_when 定义“什么才是真正失败”。

使用 block/rescue/always 可以像异常处理一样组织。

- block:
    - command: /opt/migrate.sh
  rescue:
    - command: /opt/rollback.sh
    - fail: {msg: "마이그레이션 실패 — 되돌렸습니다"}
  always:
    - command: /opt/unlock.sh

下一实验要做什么

遍历普通列表与字典列表创建目录,用 when 只处理符合条件的项;通过 loop_control.label 整理日志,并用 until 构建等待逻辑。再用两个列表的组合生成六个配置文件,通过模式重新收集,最后制作汇总报告。