Ansible 開發實戰:多個TaskQueueManager並行運行時本地臨時目錄丟失的問題

(本文基於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子進程創建之前不被引用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章