Linux中國

改進你的 Ansible 劇本的 4 行代碼

在博客圈裡,人們對基礎架構即代碼、持續集成/持續交付(CI/CD)管道、代碼審查和測試製度讚不絕口,但人們很容易忘記,這種精心設計的象牙塔只是一種理想,而不是現實。雖然不完美的系統困擾著我們,但我們必須交付一些東西。

在系統自動化的過程中,很少有比那些通過粘合 API 創建的象牙塔更脆弱的塔。這是一個脆弱的世界。要讓它「工作起來」,交付它,然後繼續前進,壓力巨大。

要解決的問題

想像一個簡單的功能請求:編寫一些 Ansible 代碼,在外部系統中創建幾條記錄,以記錄一個 VLAN 的一些詳細信息。我最近很想做一些實驗室的管理工作來完成這個任務。這個外部系統是一個常見的 互聯網協議地址管理 Internet Protocol Address Management (IPAM)工具,但對於一個更抽象的 配置管理資料庫 Configuration Management DataBase (CMDB)或一個與網路無關的記錄來說,困難是一樣的。在這個例子中,我創建一個記錄的直接願望就是讓系統保存記錄而已。

如果我們的目標是一個超緊湊的、直接的、笨拙的宏,那麼它可能用 100 行代碼就能寫出來。如果我記得 API,我也許能在一個小時內把它敲出來,該代碼的作用不會超過預期,除了確切的成品之外,什麼也沒留下。對它的目的而言是完美的,但是對未來的擴展毫無用處。

如今,我希望幾乎每個人都能從一個 角色 role 和幾個 任務 task 文件開始這項任務,準備擴展到十幾個創建、讀取、更新和刪除(CRUD)操作。因為我不了解這個 API,我可能會花上幾個小時到幾天的時間,僅僅是擺弄它,弄清楚它的內部模式和工藝,彌和它的功能和我用代碼編寫出來的意圖之間的差距。

在研究 API 的時候,我發現創建一個 VLAN 記錄需要一個父對象引用 vlan_view_ref。這看起來像一個路徑片段,裡面有隨機字元。也許它是一個哈希,也許它真的是隨機的,我不確定。我猜想,許多在泥濘中掙扎的人,在迫在眉睫的截止日期前,可能會把這個任意的字元串複製粘貼到 Ansible 中,然後繼續混下去。忽略這個 角色 role 的實現細節,顯而易見這個 劇本 playbook 級的任務應該是這樣:

- name: "Create VLAN"
  include_role:
    name: otherthing
    tasks_from: vlan_create.yml
  vars:
    vlan_name: "lab-infra"
    vlan_tag: 100
    vlan_view_ref: "vlan_view/747f602d-0381"

不幸的是,除了通過 API,vlan_view_ref 標識符是不可用的,所以即使把它移到 清單文件 inventory 或額外的變數中也沒有什麼幫助。 劇本 playbook 的用戶需要對系統有一些更深入的理解,才能找出正確的引用 ID。

在實驗室建設的情況下,我會經常重新部署這個記錄系統。因此,這個父對象引用 ID 每天都會發生變化,我不希望每次都要手動找出它。所以,我肯定要按名稱搜索該引用。沒問題:

- name: Get Lab vlan view reference
  include_role:
    name: otherthing
    tasks_from: search_for.yml
  vars:
    _resource: vlan_view
    _query: "name={{ vlan_parent_view_name }}"

最終,它進行了一個 REST 調用。這將「返回」 一個 JSON,按照慣例,為了便於在角色外訪問,我把它填充進了 _otherthing_search_result 中,。search_for.yml 的實現是抽象的,它總是返回一個包含零或多個結果的字典。

正如我讀過的幾乎所有真實世界的 Ansible 代碼所證明的那樣,大多數 Ansible 開發者將會繼續前進,好像一切都很好,並且可以直接訪問預期的單個結果:

- name: Remember our default vlan view ref
  set_fact:
    _thatthig_vlan_view_ref: "{{ _otherthing_search_result[0]._ref }}"

- name: "Create VLAN"
  include_role:
    name: otherthing
    tasks_from: vlan_create.yml
  vars:
    vlan_name: "lab-infra"
    vlan_tag: 100
    vlan_view_ref: "{{ vlan_parent_view_name }}"

但有時 _otherthing_search_result[0] 是未定義的,所以 _thatthig_vlan_view_ref 也將是未定義的。很有可能是因為代碼運行在不同的真實環境中,而有人忘記了在清單中或在命令行中更新 {{ vlan_parent_view_name }}。或者,無論公平與否,也許有人進入了工具的圖形用戶界面(GUI),刪除了記錄或更改了它的名稱什麼的。

我知道你在想什麼。

「好吧,不要這樣做。這是一個沒有啞巴的場所。不要那麼笨。」

也許我對這種情況還算滿意,反駁道:「Ansible 會很正確的告訴你錯誤是:list 對象沒有元素 0,甚至會帶個行號。你還想怎樣?」作為開發者,我當然知道這句話的意思 —— 我剛寫的代碼。我剛從三天的和 API 鬥智斗勇中走出來,我的腦子很清醒。

明天是另一個故事

但是到了明天,我可能會忘記什麼是父對象引用,我肯定會忘記第 30 行上的內容。如果一個月後出了問題,就算你能找到我,我也得花一個下午的時間重新解讀 API 指南,才能搞清楚到底出了什麼問題。

而如果我出門了呢?如果我把代碼交給了一個運維團隊,也許是一個實習生通過 Tower 來運行,把 vlan_view_name 手動輸入到表單之類的東西呢?那第 30 行出的問題是對他們沒有幫助的。

你說,加註釋吧! 嗯,是的。我可以在代碼中寫一些梗概,以幫助下周或下個月的開發人員。這對運行代碼的人沒有幫助,他的「工作」剛剛失敗,當然對於企業也無濟於事。

記住,我們此刻無所不能。在寫代碼或者跳過寫代碼的時候,我們是站在實力和知識的立場上進行的。我們花了幾個小時,甚至幾天的時間,研究了文檔、現實、其他 bug、其他問題,我們留下了代碼、注釋,甚至可能還有文檔。我們寫的代碼是分享成功的,而成功正是我們用戶想要的。但是在這種學習中也有很多失敗的地方,我們也可以留下這些。

在代碼中留言

「第 30 行有錯誤」對任何人都沒有幫助。至少,我可以用更好的錯誤信息來處理明顯的錯誤情況:

  - name: Fail if zero vlan views returned
     fail:
       msg: "Got 0 results from searching for VLAN view {{ vlan_parent_view_name }}. Please verify exists in otherthing, and is accessible by the service account."
     when: _otherthing_search_result | length == 0

在這四行代碼中(沒有額外的思考),我把具體的、有用的建議留給了下一個人 —— 那個無助的運維團隊成員,或者更有可能是一個月後的我 —— 這是關於現實世界中的問題,其實根本不是關於代碼的。這條消息可以讓任何人發現一個簡單的複製/粘貼錯誤,或者記錄系統發生了變化。不需要 Ansible 知識,不需要凌晨 3 點給開發人員發簡訊「看看第 30 行」。

但是等等!還有更多!

在了解 otherthing 的過程中,我了解到它在一個關鍵的方面,嗯,還挺笨的。它的許多記錄類型(如果不是全部的話)沒有唯一性約束,可能存在幾個相同的記錄。VLAN 視圖被定義為有一個名稱、一個開始 ID 和一個結束 ID;其他記錄類型也同樣簡單,顯然這應該是一個唯一的元組 —— 基於現實和資料庫規範化的抽象概念。但 otherthing 允許重複的元組,儘管在概念上講永遠不可能。

在我的實驗室里,我很樂意嘗試並記住不要這樣做。在企業生產環境中,我可能會寫一個策略。不管是哪種方式,經驗告訴我,系統會被破壞,會在倒霉的時候被破壞,而且可能需要很長時間才能讓這些問題發酵成,嗯,一個問題。

對於 「第 30 行有錯誤」,一個本來有豐富經驗的 Ansible 開發者可能會認識到這是「記錄沒有找到」,而不用知道其他的事情就足以解決這個問題。但如果 _otherthing_search_result[0] 只有有時是正確的 vlan_view_ref,那就糟糕多了,它讓整個世界被破壞,而悄無聲息。而這個錯誤可能完全表現在其他地方,也許六個月後的安全審計會將其標記為記錄保存不一致,如果有多種工具和人工訪問方式,可能需要幾天或幾周的時間才能發現這個特定代碼出錯的事實。

在幾天對 API 的摸索中,我學到了這一點。我不是在找問題,如果有記錄,我沒有看到。所以我來到了這篇文章的重點。我沒有因為它是一個實驗室,修復它,然後繼續前進而忽略了這種不可能的情況,而是花了兩分鐘留下了代碼 —— 不是注釋,不是心理筆記,不是文檔 —— 而是會一直運行的代碼,涵蓋了這種不可能的情況:

  - name: Fail if > 1 views returned
     fail:
       msg: "Got {{ _otherthing_search_result | length }} results from searching for VLAN view {{ vlan_parent_view_name }}. Otherthing allows this, but is not handled by this code."
     when: _otherthing_search_result | length > 1

我手動創建了失敗條件,所以我可以手動測試這個條件。我希望它永遠不會在實際使用中運行,但我覺得它會。

如果(當)這個錯誤發生在生產環境中,那麼有人可以決定該怎麼做。我希望他們能修復壞數據。如果它經常發生,我希望他們能追蹤到另一個損壞的系統。如果他們要求刪除這段代碼,而這段代碼做了未定義和錯誤的事情,那是他們的特權,也是我不想工作的地方。代碼是不完美的,但它是完整的。這是匠人的工作。

現實世界中的自動化是一個迭代的過程,它與不完美的系統進行鬥爭,並平等地使用。它永遠不會處理所有的特殊情況。它甚至可能無法處理所有的正常情況。通過 Lint、代碼審查和驗收測試的工作代碼是處理安全和所需路徑的代碼。只要付出一點點努力,你就可以幫助下一個人,不僅僅是繪製安全路徑,還可以對你發現的危險留下警告。

via: https://opensource.com/article/21/1/improve-ansible-play

作者:Jeff Warncia 選題:lujun9972 譯者:wxy 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國