(本文基于Ansible 2.7)
TaskQueueManager是Ansible调度play的基本执行单元,如果有多个TaskQueueManager并行运行,并且按照ansible官方给出的API示例清理本地的临时目录:
shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)
可能会造成FileNotFoundError
FileNotFoundError: [Errno 2]No such file or dirctory:
‘/xxx/.ansible/tmp/ansible-local-xxxxx/tmpxxxx’
这是因为Ansible本地临时目录的生成是在lib/ansible/constants.py里做的。constants会调用ConfigManager来读取本地配置:
# POPULATE SETTINGS FROM CONFIG ###
config = ConfigManager()
# Generate constants from config
for setting in config.data.get_settings():
value = setting.value
if setting.origin == 'default' and \
isinstance(setting.value, string_types) and \
(setting.value.startswith('{{') and setting.value.endswith('}}')):
try:
t = Template(setting.value)
value = t.render(vars())
try:
value = literal_eval(value)
except ValueError:
pass # not a python data structure
except Exception:
pass # not templatable
value = ensure_type(value, setting.type)
set_constant(setting.name, value)
在
value = ensure_type(value, setting.type)
创建了本地临时目录。下面是ensure_type方法中与本地临时目录创建相关的分支代码块:
elif value_type in ('tmp', 'temppath', 'tmppath'):
value = resolve_path(value, basedir=basedir)
if not os.path.exists(value):
makedirs_safe(value, 0o700)
prefix = 'ansible-local-%s' % os.getpid()
value = tempfile.mkdtemp(prefix=prefix, dir=value)
可以看到,ansible用“ansible-local-” 和当前进程的pid拼成一个前缀,并用这个前缀创建了临时目录,并赋值给constants.DEFAULT_LOCAL_TMP。那么问题来了,一旦constants在主进程被引用,本地临时目录就会在主进程被创建,后续无论是启动子进程还是子线程来实现TaskQueueManager并行,这个值都会被所有的TaskQueueManager继承。每个TaskQueueManager执行时间不定,先执行完毕的在做本地临时目录清理的时候是直接rmtree的,也就是说可能把其他TaskQueueManager产生的临时文件也删除掉了,这样就报出了上面的那个FileNotFoundError。
这个问题我觉得是因为Ansible其实主要还是在考虑将其作为一款工具来使用,而从作为远程作业调度库使用的角度来说考虑较少导致的,从设计阶段就没有考虑以TaskQueueManager的粒度来做临时文件的清理,即使不算是设计缺陷应该说也是不太完善。
从这个角度来说,较好的方案应该是本地临时目录由TaskQueueManager来创建,临时目录的绝对路径由TaskQueueManager来维护,在TaskQueueManager.cleanup方法中由TaskQueueManager来负责清理。临时目录的命名避免使用pid这种与运行环境相关的数据,而考虑使用UUID更好。这样TaskQueueManager之间互不干扰,可以杜绝误删。
但以2.7版本的源码为例,其中对DEFAULT_LOCAL_TMP项的引用超过10处(不含测试代码),在保留这个配置项的前提下,要做到这个设计首先需要从ensure_type中去掉拼接前缀和创建临时目录的步骤,改到TaskQueueManager里,这个修改的动静就比较大了,需要大量的测试。
如果稍退一步,可以从自身应用的编写上想办法
首先,采用多进程结构,确保每个进程只有一个TaskQueueManager对象被创建。
第二,保证constants在TaskQueueManager子进程创建之前不被引用。