据我所知,ansible 无法安全退出正在运行的剧本,所以我想知道哪种策略是明智的,可以不让主机处于不一致的状态:
让我们想象一下一个由大约 300 个数据库主机组成的基础设施,每周停机时间只有几个小时。周三我们想更新其中一些,运行一个剧本(注意是序列号!):
- name: patchDB.yml
hosts: dbservers
serial: 10
tasks:
updatedbs [...]
会花费太多时间,因此如果维护停机时间用完,我最终将不得不中止游戏。
我给出了 3 个选项:
- 包括在完成一批(序列号:10)后暂停并提示继续下一批(如果还有时间)或中止;PB 不再完全自动化
- 定时暂停(全自动 PB,但如果错过了暂停窗口,您将不得不等待另一批)
- 任何维护窗口的小型自定义库存/组,因此总运行时间变得可预测,例如可用 3 小时,每批补丁的估计持续时间 = 20 分钟,序列号:最多 10 个。可能(控制器上的负载)>> 180 分钟/30 分钟 = 6 批 * 10 = 每个自定义库存定义的每个维护窗口总共 60 个主机
当然,我知道 Ansible 提供了大量自定义故障条件和元操作插件,但仅仅拥有有限的维护时间并不能真正与剧本中的任何检查联系起来。我确实很怀念内置的 ansible 功能,例如“通过 ctrl+c 中止,告诉剧本完成剩余的批次,然后正常退出”。
我更倾向于 (3),因为创建自定义清单并不麻烦(只需从主 all-inventory 中 grep 任何你想要的内容即可)。你们在大规模补丁部署实践中有什么更好的实用想法或经验吗?干杯!
答案1
不管你如何按每次播放和串行的方式对主机进行批处理,在并行执行更多操作并加快速度与在小批量中执行较少操作但影响较小之间都需要进行权衡。
您可以通过在启动每个批次之前检查窗口是否已过期来改进选项 3(估计运行时间)。包括批次所需时间的估计缓冲。
---
- name: Very first play
hosts: localhost
gather_facts: false
tasks:
# Unfortunately cannot keep actual datetime objects
# as Jinja converts them to strings
# Using set_fact to not lazy evaluate; need the time now not later
- name: playbook start!
set_fact:
playbook_start: "{{ now().timestamp() }}"
- debug:
var: now
verbosity: 1
# TODO Consider checking whether now is in a downtime window on some calendar
- name: Time window respecting play
hosts: localhost,127.0.0.2
gather_facts: false
serial: 1
vars:
# Configuration, in seconds
# Realistically would be much longer
# Total duration for all hosts:
downtime_planned_duration: 5
# Estimate of time for each batch to take:
downtime_buffer: 3
pre_tasks:
- name: host start!
set_fact:
host_start: "{{ now().timestamp() }}"
# Default behavior of failed hosts is to stop and not proceed with play
# Checking this before doing anything means
# hosts are not interrupted in the middle of their work
# Failed hosts can be reported on, and run again such as with retry files
- name: check if time window expired
assert:
that: "{{ (playbook_duration | int) + (downtime_buffer | int) < (downtime_planned_duration | int) }}"
success_msg: "Still in time window, proceeding with host"
fail_msg: "Insufficent buffer in time window, not starting host"
vars:
playbook_duration: "{{ (host_start | int) - (hostvars['localhost'].playbook_start | int) }}"
tasks:
# Do work here, run some roles
- name: sleep a bit to simulate doing things
pause:
seconds: 3
不幸的是,当作为 play 实现时,这只是几行时间数学废话,不容易重复使用。理论上,这可以写成类似回调插件的东西,并在 play 事件上自动触发。