IaC Part 8-持續測試與交付
持續測試和交付是IaC的三個核心實踐中的第二個,其中還包括將所有內容定義為代碼和建置小片段(small pieces)。 測試是敏捷軟體工程的基石。XP(Extreme Programming) 強調首先使用 TDD 編寫測試,並經常使用 CI 整合代碼。 CD 將其擴展為在開發人員處理代碼時就能測試代碼在完整生產環境就緒情況,而不是等到完成發布工作。
如果在編寫應用程式代碼時對測試的高度關注可以產生良好的結果,那麼可以合理地期望它對基礎設施代碼也有用。 本文探討測試和交付基礎設施的策略。 它大量借鑒了敏捷工程品質方法,包括 TDD、CI 和 CD。 這些實踐都是透過將測試嵌入到代碼編寫過程中來將品質內建到系統中,而不是留到以後做。
為何要持續性測試基礎設施代碼?
測試基礎設施的變更其實是必要的。 但建置和維護一套測試自動化代碼的需求可能不那麼明確。 我們通常認為建立基礎設施是一個一次性作業:建造它,測試它,然後使用它。 為什麼要花精力為我們曾經建立過的東西創造自動化測試套件?
創造自動化測試套件是一項艱鉅的作業,尤其是當我們在考量實施"交付和測試"工具和服務(CI server、pipeline、test runners、test scaffolding以及各種類型的掃描和驗證工具)所需的作業時。 而當我們開始使用IaC時,建立所有這些東西似乎比建置會在基礎設施上運行的系統來得有更多的工作。
在IaC part 1中,有提到了實施用於向基礎設施交付變更的系統的基本原理。 回顧一下,在建立基礎架構後,對基礎架構進行的變更就比我們預期的要多得多。 一旦任何重要的系統上線,我們就需要進行補丁、升級、修復和
改進。
CD 的一個主要好處是消除了在傳統地端時代區別的系統生命週期「建造」和「運行」階段之間。設計和實施交付系統,包括自動化測試和代碼升級以及系統本身。 使用該系統逐步建立基礎設施,並在其整個運行生命週期中逐步改進它。 「上線」幾乎是一個隨時會發生的事件,改變的是系統的使用者,而不是系統的管理方式。
持續測試的意義
敏捷工程的基石之一是在作業中進行測試 — 品質是內建的。我們越早發現我們編寫的每一行代碼是否已準備好用於生產環境,我們的作業速度就越快,就能越早交付價值。 更快地發現問題也意味著花更少的時間回頭去調查問題以及更少的時間來修復和重寫代碼。 不斷解決問題可以避免技術債的累積。
大多數人都認識到快速回饋的重要性。 但真正高績效團隊的差異在於他們如何積極地追求真正持續性的回饋。
傳統方法是在團隊實行系統的完整功能後才進行測試。 時間盒(Timeboxed)方法進一步推動了這一點。 團隊在開發過程中定期進行測試,例如在衝刺(sprint)結束時。 遵循精實或看板的團隊在完成每個story時對其進行測試。
真正的連續測試比這種方式更頻繁的測試。 技術人員在開發時編寫並進行測試,甚至在完成story之前。 他們經常將代碼推送到集中式自動化建置系統(automated build system)中 — — 最好每天至少一次。
技術人員在推送代碼時需要盡快獲得回饋,以便他們能夠在盡可能不要去中斷作業流程的情況下做出回應。 緊密的回饋循環是持續測試的本質。
立即測試和最終測試
另一種想法是將每個測試活動分類為立即的或最終的。 當我們推送代碼時,會立即進行測試。 最終測試會在之後一段時間後進行,可能是在人工審核後,也可能是按排程進行。
理想情況下,測試是真正即時的,在編寫代碼時發生。 Editor中會進行一些驗證活動,例如syntax highlight或執行unit test。 LSP(Language Server Protocol)定義了將語法檢查整合到 IDE 中的標準,並受到各種開發語言實作的支援。
喜歡使用command line作為開發環境的人可以使用 inotifywait 或 entr 等工具在代碼變更時在terminal中執行檢查。
立即驗證的另一個例子是pair programming,這本質上是在作業中進行的代碼審查。 Pairing提供的回饋比我們完成story或feature後進行的代碼審查要快得多,而其他人會抽出時間來審查我們所做的事情。
每次有人將變更推送到代碼庫時,CI build和 CD pipeline都應該立即運作。 立即執行每個變更不僅可以讓他們更快地獲得回饋,還可以確保每次運作的變更範圍較小。 如果pipeline只有定期運行,則它可能包含多人的多項變更。 如果任何測試失敗,就很難確定是哪個變更導致了問題,這意味著需要更多的人參與並花時間來尋找和修復它。
我們應該在基礎設施中測試甚麼?
CI 的本質是盡快測試某人所做的每一個變更。 CD 的本質是最大化測試範圍。 正如 Jez Humble 所說:「我們透過確保我們的代碼始終處於可部署狀態來實現這一切。
品質保證(Quality assurance)是關於管理將代碼套用到系統中的風險。 當套用時服務會中斷嗎? 它是否建立了正確的基礎設施? 基礎設施是否按照應有的方式運作? 它是否符合效能、可靠性和安全性的運作標準? 它是否符合監管和治理規則?
CD 主要在擴大將變更推送到代碼庫時立即測試的風險範圍,而不是等待數天、數週甚至數月後的最終測試。 因此,每次推送時,Pipeline都會將代碼套用到實際的測試環境中並運行全面性測試。 理想情況下,一旦代碼運行完整的pipeline的自動化階段,它就被充分證明可以投入生產環境。
團隊應該識別對其基礎設施代碼進行變更所帶來的風險,並建立一個可重複的流程來針對這些風險測試任何特定的變更。 此流程採用自動測試套件和手動測試的形式。 測試套件是作為一組運行的自動化測試的集合。
當我們考慮自動化測試時,我們通常會想到功能測試,例如Unit test和 UI-driven journey tests。 但風險範圍比功能缺陷更廣泛,因此驗證的範圍也更廣泛。 超出純功能性的限制和要求通常稱為非功能性需求
(NFR-Non-Functional Requirements) 或跨職能需求 (CFR-Cross-Functional Requirements)。 我們可能想要自動或手動驗證的項目可能包括:
代碼品質
代碼是否可讀、可維護? 它是否遵循團隊關於如何格式化和結構化代碼的標準? 根據我們使用的工具和語言,某些工具可以掃描代碼以尋找語法錯誤和格式規則合規性,並執行複雜性分析。 根據它們存在的時間和經常使用的程度,基礎設施語言可能沒有很多這一類的工具。 人工審查方法包括gated code review processes、code showcase sessions與pair programming。
功能性
它做了它該做的事嗎? 最終,透過將應用程式部署到基礎設施並檢查它們是否正確運行來測試功能。 這樣做可以間接測試基礎架構是否正確,但我們通常可以在部署應用程式之前發現問題。 基礎設施的一個例子是網路路由。 可以從網際網路到 Web server建立 HTTPS connections嗎? 我們也許可以使用整個基礎架構的子集來測試這類事情。
安全性
我們可以在各個層級測試安全性,從代碼掃描到unit test,再到整合測試和生產環境監控。 有一些特定於安全測試的工具,例如漏洞掃描。 將安全測試寫入標準測試套件也可能很有用。 例如,unit test可以對開放的port、使用者帳號處理或存取權限進行診斷。
合規性
系統可能需要遵守法令法規、產業標準、合約義務或組織政策。 對於基礎設施和維運團隊來說,確保和證明其合規性可能非常耗時。 自動化測試對此非常有用,既可以快速發現不合規行為,也可以為稽核人員提供證據。 與安全性一樣,我們可以在從代碼層級到生產測試的多個驗證層級上執行此操作。
效能
自動化工具可以測試特定操作的完成速度。 如果在部署應用程式之前執行測試從 A 點到 B 點的網路連線速度,可能會暴露網路配置或雲端平台的問題。 尋找系統子集上的效能問題是如何獲得更快回饋的另一個範例。
可擴充性
自動化測試可以證明擴充作業正常; 例如,檢查自動擴充的叢集是否在需要時新增節點。 測試還可以檢查擴充是否能帶來預期的結果。 例如,由於系統其他地方存在瓶頸,因此向叢集新增節點可能不會提高容量。 頻繁地執行這些測試意味著我們很快就會發現基礎架構的變更是否會破壞擴充功能。
可用度
同樣,自動化測試可以證明系統在面臨潛在中斷時仍然可用。 測試可以破壞資源(例如刪除叢集的節點),並驗證叢集是否自動替換它們。 還可以測試未自動解決的場景是否已妥善處理; 例如,顯示錯誤頁面(error page)並避免資料損毀。
運作能力
我們可以自動測試運作所需的任何其他系統需求。 團隊可以測試監控(inject error並證明監控可以偵測到並回報錯誤)、日誌記錄和自動維護活動。
測試基礎設施代碼的挑戰
大多數使用IaC的團隊都在努力為其基礎設施代碼實行與應用程式代碼相同層級的自動化測試和交付。 許多沒有敏捷軟體工程背景的團隊發現這更加困難。
IaC的前提是我們可以將敏捷測試等軟體工程實務應用到基礎架構中。 但基礎設施代碼和應用程式代碼之間存在顯著差異。 因此,我們需要調整應用程式測試中的一些技術和思維方式,使其適用於基礎架構。
以下是基礎設施程代碼和應用程式代碼之間的差異所帶來的一些挑戰。
挑戰一: 測試宣告代瑪的價值通常很低
如IaC part 4中所提到的,許多基礎設施工具使用宣告式語言而不是命令式語言。 宣告式代碼通常聲明某些基礎設施的所需狀態,例如以下碼定義了子網路:
sunnet:
name:private_A
address_range: 192.168.0.0/16
對此的測試只需重述代碼:
assert:
subnet("private_A").exists
assert:
subnet("private_A").address_range is("192.168.0.0/16")
一套宣告式代碼的低階測試可以成為一項記帳練習。 每次變更基礎設施代碼時,我們都會變更測試來檢查符合程度。 這些測試提供什麼價值? 測試就是管理風險,所以讓我們考量一下前面的測試可以發現哪些風險:
- 基礎設施代碼從未被套用
- 基礎設施代碼已套用了,但該工具未能正確應用它,且未返回錯誤
- 有人更改了基礎設施代碼,但忘記更改測試來匹配
第一個風險可能是真實存在的,但不需要對每一個宣告進行測試。 假設代碼在伺服器上執行多個運作,那麼一次測試就足以表明,無論出於何種原因,代碼都沒有被應用。
第二個風險歸結為保護我們免受所使用工具中的錯誤影響。 該工具開發人員應該修復該錯誤,或者團隊應該改用更可靠的工具。 有些團隊在發現特定錯誤並希望保護自己免受該錯誤影響時使用這樣的測試。對此進行測試可以覆蓋已知問題,但是用詳細的測試來覆蓋代碼以防萬一工具出現錯誤是浪費的。
最後一個風險是循環邏輯。 刪除測試將消除它所解決的風險,並且還會減少團隊應該要進行的作業。
在某些情況下測試宣告式代碼是有效的。 有兩個問題是宣告式代碼何時會呈現不同的結果,以及何時組合多個宣告。
測試變數(variables)宣告代碼
上面的宣告式代碼範例很簡單 — 參數值是hardcode的,因此套用代碼的結果很清楚。 變數引入了產生不同結果的可能性,這可能會產生使測試更有用。 變數不會永遠產生需要測試的變化。 如果我們在前面的範例中加入一些簡單的變數會怎麼樣?
subnet:
name: ${jason_WEB}-${jason_ENVIRONMENT}
address_range: ${SUBNET_IP_RANGE}
上面這段代碼沒有太大的風險,尚未由應用它的工具管理。 如果有人將變數設為無效值,該工具就會失敗並出現錯誤。
當可能的結果越多時,代碼的風險就越大。 讓我們在範例中加入一些條件代碼:
subnet:
name: ${jason_WEB}-${jason_ENVIRONMENT}
address_range: get_networking_subrange(
get_vpc(${jason_ENVIRONMENT}),
data_centers.howmany,
data_centers.howmany++
)
這段程式碼有一些可能值得測試的邏輯。 它會呼叫兩個函數 get_networking_subrange 和 get_vpc,其中任何一個都可能失敗或傳回與另一個函數以意外方式互動的結果。套用此代碼的結果會根據輸入和上下文而變化,這使得編寫測試是值得的。
測試宣告式代碼的組合
另一種測試更有價值的情況是當有多個基礎設施宣告組合成更複雜的結構時。 例如,我們定義多個網路結構的代碼 —address block/load balancer,/routing rules/gateway。 每段代碼可能都很簡單,因此無需進行測試。 但這些的結合產生了一個值得測試的結果 — — 某人可以建立從 A 點到 B 點的網路連線。
測試該工具是否建立了代碼中宣告的內容通常不如測試它們是否實現了我們想要的結果有價值。
挑戰二:測試基礎設施代碼是緩慢的
要測試基礎設施代碼,我們需要將其應用到相關基礎架構中。 配置infra instance通常很慢,尤其是在雲端平台上。 大多數努力實施自動化基礎設施測試的團隊發現建立測試基礎設施的時間是快速回饋的障礙。解決方案通常是策略的組合:
將基礎設施分解成一個個小部件
將可測試性作為設計系統結構的因素是有用的,因為它是使系統易於維護、擴展和發展的關鍵方法之一。 使部件更小是一種策略,因為較小的部件通常可以更快地配置和測試。 為更小、更鬆散耦合的部分編寫和維護測試更容易,因為它們更簡單且風險更小。
澄清、最小化和隔離依賴關係
系統的每個元素可能依賴系統的其他部分、平台服務以及團隊、部門或組織外部的服務和系統。 這些會影響測試,特別是當需要依賴其他人提供instance來支援我們的測試時。 它們可能速度慢、成本高、不可靠或測試資料不一致,尤其是在其他使用者共享它們的情況下。 測試替身(Test doubles)是隔離組件的有用方法,以便可以快速測試它。 我們可以使用測試替身作為漸進測試策略的一部分 — 首先使用測試替身測試部件,然後將其與其他部件和服務整合進行測試。
逐步性測試
我們通常會有多個測試套件來測試系統的不同面向。 我們可以先執行更快的測試,以便在失敗時獲得更快的回饋,然後在測試通過後才執行更慢、更廣泛的測試。
暫時性或永久性Instance的選擇
我們可以在每次測試時建立和銷毀基礎設施的一個insatnce(臨時性),也可以在兩次運行之間保留一個正在運行的insatnce(永久性)。 使用臨時instance使測試明顯變慢,但有更乾淨並提供更一致的結果。 保持永久insatnce可以減少執行測試所需的時間,但可能會留下變更並隨著時間的推移累積不一致的情況。 為一組特定的測試選擇適當的策略,並根據其效果重新審視決策。
線上與線下測試
某些類型的測試在線上運行,要求在「真實」雲端平台上配置基礎架構。 其他人可以在本地端上離線運行。 可以離線運行的測試包括代碼語法檢查和在VM或container instance中執行的測試。 考慮各種測試的性質,並了解哪些測試可以在哪裡運行。 離線測試通常要快得多,因此我們傾向於更早運行它們。 可以使用測試替身來離線模擬雲端 API 進行某些測試。
對於這些策略,我們都應該定期評估它們的成效如何。 如果測試不可靠,要嘛無法正確運行,要嘛返回不一致的結果,那麼我們應該深入研究原因並修復它們或用其他東西替換它們。 如果測試很少失敗,或者相同的測試幾乎總是一起失敗,那麼我們可以將它們刪除以簡化測試套件。 如果花費更多的時間來尋找和修復源自測試而不是正在測試的代碼中的問題,請尋找簡化和改進它們的方法。
挑戰三:依賴關係使測試基礎設施變得複雜
設定代碼所依賴的其他基礎設施所需的時間會使測試變得更加緩慢。 解決這個問題的一個有用技術是用測試替身替換依賴項。Mocks、fakes和stubs都是測試替身的類型。 測試替身替換了組件所需的依賴項,以便可以單獨測試它。 這些術語往往被不同的人以不同的方式使用。
在基礎架構環境中,越來越多的工具可讓我們模擬CSP的 API。我們可以將基礎架構代碼套用到本機模擬雲端來測試代碼的某些面向。 這些不會告訴我們的網路架構是否正常運作,但它們應該告訴我們它們是否大致有效。
對其他基礎設施元件使用測試替身通常比對基礎設施平臺本身更有用。 IaC Part 9會給出使用測試替身和其他測試裝置來測試Infra Stack的範例。
漸進式測試
大多數重要的系統使用多套測試來驗證變更。 不同的套件可能測試不同的東西。 一套套件可以離線測試某一問題,例如透過掃描代碼語法來檢查安全漏洞。 另一個套件可以針對相同的問題執行線上檢查,例如透過探測Infra stack的running instance是否存在安全漏洞。
漸進式測試涉及按順序運行測試套件。 這個序列逐漸建立,從在較小範圍的代碼上運行更快的更簡單的測試開始,然後在更廣泛的整合元件和服務上建立更全面的測試。 測試金字塔(test pyramid)和瑞士起司測試(Swiss cheese testing)等模型可幫助我們思考如何在測試套件中建立驗證活動。
漸進式回饋策略的指導原則是獲得快速、精確的回饋。 通常,這意味著首先運行速度更快、範圍更窄、依賴性更少的測試,然後執行逐步添加更多元件和整合點的測試(如下圖)。 這樣,小錯誤很快就會顯現出來,以便可以快速修復和重新測試。
當廣泛的測試失敗時,我們需要調查大量部件和相依性。 因此,應該儘早嘗試以最小的範圍找到任何潛在區域的問題。
測試策略的另一個目標是保持整個測試套件的可管理性。 避免在不同等級重複測試。 例如,可以測試應用程式伺服器配置碼是否在日誌資料夾上設定了正確的目錄權限。 該測試將在明確測試伺服器配置的早期階段運行。 而不應該在測試雲端中配置的完整基礎架構堆疊的階段進行檢查檔案權限的測試。
測試金字塔(test pyramid)
測試金字塔是眾所周知的軟體測試模型。 測試金字塔的關鍵思維是 — 我們應該在較低層級(即進展的早期階段)進行更多測試,而在後期階段進行更少的測試(如下圖)。
金字塔是為應用軟體開發而設計的。 金字塔的下層由unit test組成,每個unit test測試一小段代碼,並且執行速度非常快。 中間層是整合測試,每個測試都涵蓋組裝在一起的組件的集合。 更高的階段是旅程測試,透過使用者介面驅動,對整個應用程式進行測試。
金字塔較高層級的測試涵蓋較低層級已涵蓋的相同範圍。 這意味著它們可能不太全面 — — 它們只需要測試組件整合中出現的功能,而不是證明較低層級組件的行為。
對於宣告式基礎設施代碼庫,測試金字塔的價值較低。 大多數為 Terraform 和 CloudFormation 等工具編寫的低階宣告式堆疊代碼對於unit test來說太大,並且依賴基礎架構平台。 宣告性模組很難以有用的方式進行測試,這既是因為測試宣告性程式碼的價值較低,也是因為如果沒有基礎設施,通常沒有太多可以有效測試的內容。
這意味著,儘管我們幾乎肯定會進行低階基礎設施測試,但可能沒有金字塔模型建議的那麼多。 因此,宣告性基礎設施的基礎設施測試套件最終可能看起來更像鑽石形狀(如下圖)。
金字塔可能與基礎設施代碼庫更相關,該基礎設施代碼庫更多地使用以命令式語言(imperative languages)編寫的動態庫(dynamic libraries)。 這些代碼庫有更多的小部件,會產生不同的結果,因此有更多的東西需要測試。
瑞士起司測試模型
思考如何組織漸進測試的另一種方法是瑞士起司模型。 這種風險管理的概念來自軟體業之外。 這個想法是,特定的測試層可能存在漏洞,就像一片瑞士起司一樣,可能會錯過缺陷或風險。 但當將多層組合起來時,它看起來更像是一塊瑞士奶酪,沒有任何孔洞貫穿。
在考慮基礎設施測試時使用瑞士起司模型的要點是,要專注於在哪裡捕獲任何特定的風險(如下圖)。 我們仍然希望在可行的情況下在最早的層級中捕獲問題,但重要的是它在整個模型中的某個位置進行了測試。
關鍵要點是基於風險而不是基於配合公式進行測試。
Infrastructure Delivery Pipelines
CD Pipeline將漸進式測試的實現與生產路徑中跨環境的代碼交付結合起來。IaC part 13與19會深入介紹了pipeline如何打包、整合程式碼並將代碼應用到環境中。 這裡介紹如何設計漸進測試的pipeline。
當有人將代碼變更推送到source control repo時,團隊使用集中系統通過一系列階段推進變更,以測試和交付變更。 儘管有人可能參與觸發或批准作業,但此流程是自動化的。
Pipeline自動化了打包、促進(promoting)、套用代碼和測試所涉及的流程。 人工可以審查變更,甚至對環境進行探索性測試。 但他們不應該手動執行命令來部署和套用變更。他們也不應該即時選擇配置選項(configuration options)或做出其他決定。 這些操作應定義為代碼並由系統執行。
流程自動化可確保每個階段的每次執行都一致。 這樣做可以提高測試的可靠性,並在Infra insatnce之間建立一致性。
每個變更都應該從管Pipeline的入口端就推送。 如果我們在Pipeline的「下游」(之後)階段發現錯誤,請不要在該階段修復它,而是繼續完成pipeline的其餘部分。 相反的,應修復repo中的代碼並從Pipeline的開頭推送新的變更,如下圖所示。 這種做法可確保每個變更都經過充分測試。
在上圖中,一項變更已成功通過Pipeline。 第二次變更在Pipeline中間失敗。 在Pipeline的第三次運行中進行了修復並推送到生產環境中。
Pipeline Stages
Pipeline的每個階段可以做不同的事情並且可以以不同的方式觸發。 特定Pipeline stage的一些特徵包括:
- Trigger —
讓Stage開始運作的事件。 當變更被推送到代碼庫時,或在pipeline中成功執行其先前的階段時,它可能會自動運行。 或者,當測試人員或發布經理決定將代碼變更套用至特定環境時,有人可能會手動觸發該階段。 - Activity —
Stage運行時會發生什麼。 一個Stage可以執行多個操作。 例如,某個stage可能會套用代碼來設定基礎架構堆疊、執行測試,然後刪除該堆疊。 - Approval —
如何將Stage標記為成功或失敗。 當命令運行沒有錯誤且自動化測試全部通過時,系統可以自動將階段標記為成功。 或者人工可能需要將Stage標記為已批准。 例如,測試人員可以在對變更進行探索性測試後批准該Stage。 我們也可以使用手動審核Stage來支援治理簽核。 - Output —
Stage產生的工件(Artifacts)或其他material。 典型的輸出包括基礎設施代碼包或測試報告。
Stage中測試的組件範圍
在漸進式測試策略中,早期階段驗證各個部件,而後期階段則整部件並將它們一起測試。 下圖顯示了逐步測試導致 Web server作為更大堆疊(stack)的一部分運行部件的範例。
一個階Stage可能會執行多個組件的測試,例如一unit tets。 或者,不同的組件可能各自具有單獨的測試階段。 IaC part 17概述了在基礎設施堆疊的背景下何時整合不同部件的不同策略。
用於Stage的依賴關係範圍
系統的許多元素依賴其他服務。 應用程式伺服器堆疊(application server stack)可能連接到身份管理服務來處理使用者身份驗證。 要逐步測試這一點,可以先運行一個Stage,在沒有身分管理服務的情況下測試應用程式伺服器,也許使用模擬服務來取代它。 稍後的Stage將在與身分識別管理服務的測試實instance整合的應用程式伺服器上執行額外的測試,生產階段將與生產insatnce整合(如下圖)。
PS:
避免在Pipeline中建立不必要的Stage,因為每個Stage都會增加交付過程的時間和成本。 因此,不要僅僅為了完整性而為每個組件和整合建立單獨的Stage。 只有當它增加了足夠的價值高於代價時,才以這種方式將測試分為多個Stage。 可能促使這樣做的一些原因包括速度、可靠性、成本和控制。
Stage所需的平台元素
平台服務是系統的一種特殊類型的依賴關係。 系統最終可能在基礎設施平台上運行,但我們也許能夠有效地離線運作和測試其中的部分內容。
例如,定義網路架構的代碼需要在雲端平台上配置這些架構以進行有意義的測試。 但是我們也許能夠測試在本機虛擬機器甚至容器中安裝應用程式伺服器套件的代碼,而不需要在雲端平台上建立虛擬機器。
因此,早期的測試階段可能無需使用某些組件的完整雲端平台即可運作(如下圖)。
Delivery Pipeline Software and Services
我們需要軟體或託管服務來建立。Pipeline system需要做以下的事情:
- 提供一種配置Pipeline stage的方法。
- 不同操作的觸發階段,包括自動事件和手動觸發。 該工具應支援更複雜的關係,例如fanning in(一個Stage具有多個input stage)和fanning out(一個stage具有多個output stage)。
- 支援Stage可能需要的任何操作,包括應用基礎架構代碼和執行測試。我們應該能夠建立客製活動,而不是只有一組固定的支援活動。
- 處理Stage的工件(Artifacts)和其他output,包括能夠將它們從一個stage傳遞到下一個階stage。
- 幫助追蹤和關聯代碼、工件、輸出和基礎架構的特定版本和instance。
Pipeline System有以下幾種選擇:
- Build server —
使用 Jenkins、Team City、Bamboo 或 GitHub Actions 等建立伺服器來建立pipeline。 這些通常是「以工作為導向」而不是「以流程為導向」。 核心設計本質上並不關聯代碼、工件的版本,並且跨多個作業運行。 大多數這些產品都在其 UI 和配置中加上了對pipeline的支援。 - CD 軟體 —
CD 軟體是圍繞Pipeline概念構建的。 將每個階Stage定義為Pipeline的一部分,並且代碼版本和工件與pipeline相關聯,以便可以向前和向後追蹤它們。 CD 工具包括 GoCD、ConcourseCI 和 BuildKite。 - SaaS service —
託管 CI 和 CD 服務包括 CircleCI、TravisCI、AppVeyor、Drone 和 BoxFuse。 - 雲端平台服務 —
大多數CSP都提供 CI 和 CD 服務,包括 AWS CodeBuild (CI) 和 AWS CodePipeline (CD) 以及 Azure Pipelines。 - Source code repository services —
許多source code repo產品和供應商都加上了 CI 功能,可以使用它來建立pipeline。 兩個有名的例子是 GitHub Actions 以及 GitLab CI 和 CD。
IaC是一個快速變化的領域,因此當您閱讀本文時,這些工具的介紹可能已經過時。 但值得看看現在存在的工具,為評估工具的出現和發展提供背景:
- Atlantis可協助我們管理 Terraform project的pull requests,並為單一instance執行plan 與 apply。 它不執行tests,但我們可以使用它來建立有限的pipeline來處理基礎設施變更的代碼審查和批准。
- Terraform Cloud包含比 CI 和pipeline更多的功能(例如module registry)。 我們可以使用 Terraform cloud 建立有限的pipeline,用於規劃project的代碼並將其套用到多個環境。 但除了使用 HashiCorp 自己的 Sentinel 產品進行策略驗證之外,它不會執行其他測試。
- WeaveWorks 提供用於管理 Kubernetes 叢集的產品和服務。 其中包括用於管理叢集配置變更交付的工具以及使用基於 Git brnches的pipeline的應用程序,這種方法稱為 GitOps。
在生產環境中測試
在將發布和變更套用於生產環境之前對其進行測試是軟體產業的一大焦點。 隨著系統複雜性和規模的增加,我們實際上可以在生產環境之外檢查的風險範圍會縮小。 這並不是說在將變更套用於生產環境之前測試變更沒有價值。 但相信預發布測試可以全面涵蓋風險會導致:
- 過度投資預發布測試(prerelease testing),遠超過了收益損益點
- 生產環境的測試投資不足
什麼是無法從生產環境複製的?
生產環境的一些特徵是我們無法在生產環境之外實際複製的:
- Data — 生產系統可能有比我們可以複製的更大的資料集,並且毫無疑問會因為使用者而具有意想不到的資料值和組合。
- Users — 由於數量龐大,使用者在做奇怪的事情方面比測試人員更有創造力。
- Traffic — 如果系統的流量非常大,我們就無法複製它經常經歷的活動的數量和類型。 與一年的生產環境運作相比,為期一周的浸泡測試(soak test)微不足道。
- Concurrency — 測試工具可以模擬多個使用者同時使用系統,但它們無法複製使用者同時執行的不尋常的操作組合。
這些特性帶來的兩個挑戰是,它們會帶來無法預測的風險,並且會造成無法很好地複製在生產環境以外的任何地方進行測試的條件。透過在生產環境中運行測試,我們可以利用那裡存在的條件 — — 大型自然資料集和不可預測的concurrent activity。
為什麼要在生產環境以外的地方進行測試?
顯然,在生產環境中進行測試並不能取代將變更套用到生產環境之前對其進行測試。 提前明確您實際上可以測試什麼是有幫助的:
- 它可以運作嗎?
- 代碼可以運作嗎?
- 它會以我可以預測的方式失敗嗎?
- 它會像以前那樣失敗嗎?
在生產環境之前測試變更可以解決已知的未知問題(known unknowns),即我們知道可能會出錯的事情。 測試生產環境中的變更解決未知的未知問題(unknown unknowns),更難以預測風險。
管理生產環境中測試的風險
生產環境中的測試會產生新的風險。 有一些事情可以幫助管理這些風險:
監控
有效的監控讓我們確信可以偵測到測試引起的問題,進而可以快速阻止它們。 這包括偵測測試何時引起問題。
可觀察性
可觀察性(Observability)使我們能夠詳細了解系統內發生的情況,幫助我們快速調查和解決問題,並提高可測試內容的品質。
零中斷佈署
能夠快速、無縫地部署和rollback 變更有助於降低錯誤風險。
漸進式佈署
如果我們可以同時執行不同版本的組件,或者對不同的使用者群組有不同的配置,則可以在向使用者公開之前測試生產環境條件的變化。
資料管理
生產環境測試不應對資料進行不當更改或暴露敏感資料。 我們可以維護不會觸發實際操作的測試資料記錄,例如使用者和信用卡號。
渾沌工程(Chaos engineering)
透過故意注入已知類型的故障來證明緩解系統正常運作,降低生產環境中的風險。