TT Lab
开始
学习 学习路径 课程

Ansible 实战

不先测量就动手,改的永远是你早就知道的那一处

在 TT Lab 中继续学习

一句话总结

playbook 慢,原因通常是五种之一,修法各不相同——所以要用 profile_tasks 先测量,然后再选旋钮。而且每个旋钮都有代价。

为什么需要它

“部署要花 40 分钟”这样的报告,哪个团队都会收到。但这 40 分钟里混着性质完全不同的时间。

如果不测量就开始修,人们会去修自己熟悉的地方。通常是调高 forks 就结束。但如果瓶颈是 fact 收集,调高 forks 几乎不会变快,反而只会增加控制节点的内存和目标主机的负载。测量不是性能优化工作的热身,而是工作本身的第一步。

工作原理

先测量——profile_tasks

Ansible 的 callback 是接收运行过程中发生的事并生成输出的插件。开启 ansible.posix.profile_tasks,会打印每个 task 的耗时,并在运行结束时再给出一份按耗时排序的摘要。不需要安装任何东西,一行配置即可。

[defaults]
callbacks_enabled = ansible.posix.profile_tasks

摘要是以 task 为单位的。所以它能告诉我们“哪个 task”,却不会告诉我们“那个 task 里的什么”。接下来就是把下面的旋钮一个个试过去。

旋钮一——要收集多少 fact

gather_facts: true 相当于在 play 的最前面悄悄插入一个 setup task。这个 task 会把目标主机的 CPU、内存、磁盘、网络接口、挂载列表全部抓回来。返回上百个 fact,实际用到的通常只有 ansible_distribution 一个。

选择的办法有两种。

旋钮二——fact 缓存,以及虚假 fact

开启 fact_caching,一旦收集过的 fact 就会存到文件或 Redis 中,下次运行时重复使用。jsonfile 缓存会为每台主机留下一个 JSON 文件,可以直接打开查看,用来学习也很合适。与 gathering = smart 搭配,就成了“缓存里有就不再询问”。

这里有本模块最重要的代价。不再询问,意味着目标主机变了,Ansible 也不知道。 昨天存进缓存的磁盘容量、内核版本、本地 fact,今天原样信任并据此判断。如果条件语句在看 fact,那个条件就是靠昨天的事实分出分支的。实际工作中的防御有三种——把缓存有效时间设得比部署周期更短、在部署流水线的第一步用 --flush-cache 清空、以及不要用缓存的 fact 做有风险的判断。

旋钮三——SSH 往返与 pipelining

关闭 pipelining 时,Ansible 会为每个 task 把模块文件复制到目标主机、执行、再删除。SSH 往返好几次。开启后,把模块通过远端 Python 的标准输入送进去,把往返减少到一次。通常这是最便宜、收益最大的一项。

默认关闭是有原因的。如果目标主机的 sudoers 中开着 requiretty,pipelining 就无法工作。所以 Ansible 把它设成了“能用的地方再开”。这项设置放在 [ssh_connection] 一节中,是否真的生效,要用 ansible-config dump --only-changed -t all 来看——没有 -t all,连接插件的设置是看不到的。

旋钮四——forks 与 strategy

两者是不同的轴。

决定什么 默认值
forks 同时连接的目标数 5
strategy 各主机在每个 task 上是否互相等待 linear

forks 是 5 而目标有 200 台时,Ansible 会分成 40 批运行。调高会变快,但控制节点的 CPU 和内存,以及打开的 SSH 连接数也会随之上升。

strategy: linear 要求所有主机完成一个 task 才能进入下一个。free 则是每台主机按自己的速度一路跑到底。在一台慢主机拖住整体的情况下,free 大幅取胜。不过 free 会打破主机之间的顺序约定。 像“先在所有 Web 服务器上摘除,再部署”这样的 playbook,在 free 下含义就崩塌了。与 serial 或 run_once 混用时尤其要小心。

旋钮五——异步

async 和 poll 是一对。poll 不为 0 时,Ansible 会在原地等待;为 0 时只拿到作业编号就直接去下一个 task。之后用 async_status 收取。

- name: Start the long job
  ansible.builtin.command: /opt/bin/reindex
  async: 1800
  poll: 0
  register: job

async 的值是允许那项作业运行的最长时间。设得太短,本来没问题的作业会被中途切断。这种方式适合“耗时长但彼此独立的作业”,如果用在需要立刻得到结果的作业上,只会让 playbook 变复杂。

在现场相遇的样子

第一,最大的收益通常来自 fact。 目标越多,fact 收集线性增加,而其中大部分用不上。一行 gather_facts: false 往往比把 forks 翻倍收益更大。

第二,一定要确认设置是否生效。 ansible.cfg 可能有多个位置,而只有一个会被使用。 改完之后在另一个目录运行,结果什么也没变,就说“没效果啊”,这种事很常见。ansible-config dump --only-changed 的 CONFIG_FILE 行会显示使用的是哪个位置。

第三,区分测量时间的位置和判定的位置。 在仿真环境或共享 runner 上,同一个 playbook 的运行时间会波动到两倍。所以证明“变快了”时,留下的不是一次运行的时间,而是结构已经改变的证据——配置转储、缓存文件、callback 留下的测量文件之类。

第四,把读取测量结果的习惯做成工具。 用眼睛扫一遍摘要,与提取前几名并留存记录是不同的。记录积累起来,就能说出“这周变慢的 task”,从那时起,性能才成为被管理的东西。

参考文档

下一项实验要做什么

先用 profile_tasks 留下基线,再把旋钮一个个拧过去。把收集全部 fact 的结果和只收集最小集合的结果并排保存,数一数缺了什么;开启 jsonfile 缓存后,修改目标主机的本地 fact,亲自确认缓存会原样返回旧值,再用 --flush-cache 解除。开启 pipelining,用配置转储确认它真的生效,应用 forks 和 strategy: free,把长作业用 poll: 0 扔出去,之后用 async_status 收回。最后亲手做一个小工具,从测量结果中提取最慢的 task。