IaC Final Part-安全地變更基礎設施
頻繁、快速地做出改變的主題貫穿了本系列文章的始終。 正如在”
基礎設施即代碼(IaC) Part-1 概念”一文中所提到的(反對意見:我們必須在速度和品質之間做出選擇),速度不僅不會使系統變得不穩定,反而會促進穩定性,反之亦然。 口頭禪不是“快速行動,打破現狀”,而是“快速行動,改進現狀”。
然而,穩定性和品質並不是純粹從速度優化而來的。 IaC Part 1中引用的研究表明,嘗試優化速度或品質不會實現任何目標。 關鍵是要針對兩者進行最佳化。 專注於能夠頻繁、快速、安全地進行更改,以及快速檢測錯誤並從中恢復。
本系列的所有內容(從使用代碼一致性地建立基礎設施,到使測試成為作業的連續部分,再到將系統分解為更小的部分)都可以實現快速、頻繁且安全的變更。
但頻繁更改基礎設施給提供不間斷的服務帶來了挑戰。 本文將探討這些挑戰以及解決這些挑戰的技術。 支持這些技術的思維不是將變化視為對穩定性和連續性的威脅,而是利用現代基礎設施的動態特性。 利用本系列文章中所描述的原則、實務和技術來最大限度地減少變化造成的干擾。
減少變更的範圍
敏捷、XP、精實和類似的方法透過小增量的改變來優化交付的速度和可靠性。 規劃、實施、測試和debug一個小的變更比一個大的變更更容易,因此我們的目標是減少批量大小。 當然,我們經常需要對我們的系統進行重大更改,但我們可以透過將事情分解為一小組可以一次交付一個的更改來實現這一點。
例如,ShopSpinner 團隊最初使用單一基礎架構堆疊來建構其基礎架構。 該堆疊包括其 Web 伺服器叢集和應用程式伺服器。 隨著時間的推移,團隊成員添加了更多的應用程式伺服器,並將其中一些變成了叢集。 他們意識到在單一 VLAN 中運行 Web 伺服器叢集和所有應用程式伺服器的設計很糟糕,因此他們改進了網路設計並將這些元素轉移到不同的 VLAN 中。 他們也決定將基礎設施分成多個堆棧(stacks),以便更容易單獨更改它們。
ShopSpinner 最初的實作是具有單一 VLAN 的單一堆疊(如下圖)。
該團隊計劃將其堆疊分成多個堆疊。 其中包括本系列其他文章範例中看到的sharednetworking-stack與application-infrastructure-stack。 該計劃還包括一個用於管理前端 Web 伺服器的容器叢集的web-cluster-stack,以及一個用於管理每個應用程式的資料庫執行個體的application-database-stack(如下圖)。
團隊還將其單一 VLAN 拆分為多個 VLAN。 應用程式伺服器將分佈在這些 VLAN 中以實現冗餘(如下圖)。
"IaC Part 17-使用堆疊作為組件"一文描述了劃分這些範例堆疊的設計選擇和一些實作模式。 現在我們可以探索在生產系統中從一種實作轉移到另一種實現的方法。
小量變更
在代碼中造成的最大混亂是在Push之前在本地構建(build)了太多工作。 專注於完成想要完成的全部工作是很誘人的。 做出一個只會讓我們朝著完整的目標更進一步的小量變更是更困難的。 將大的變更轉為一系列小量變更來實施需要新的思考方式和新的習慣。
幸運的是,軟體開發世界已經指明了道路。 本系列文章中介紹了許多支援一次建置系統的技術,包括 TDD、CI 和 CD。 使用流水線逐步測試和交付代碼變更(如"IaC Part 8-持續測試與交付"中所述並在整本系列文章中引用)是一個推動因素。 我們應該能夠對代碼進行小量的變更,push它,獲得有關其是否有效的回饋,然後將其投入生產。
使用這些技術的團隊可以非常頻繁地有效推動變更。 單一工程師可能每小時左右推送一次變更,每個變更都會整合到主代碼庫中,並在完全整合的系統中測試生產準備。
技術人員透過各種術語和技術來將重大變更轉化為一系列的小量邊更:
Incremental
增量變更是新增計劃實行的一部分的變更。 可以透過一次實作一個堆疊來增量建置範例 ShopSpinner 系統。 首先,建立共享網路堆疊。 然後,新增 Web 叢集堆疊。 最後,建立應用程式基礎架構堆疊。
Iterative
迭代變更使系統逐步改進。 透過創建所有三個堆疊的基本版本來開始建立 ShopSpinner 系統。 然後進行一系列變更,每一個變更都擴展堆疊的功能。
Walking skeleton
運作骨架是新系統主要部分的基本實現,實現它是為了幫助驗證其整體設計和結構。 技術人員經常為基礎設施專案創建一個運作骨架,以及將在其上運行的應用程式的類似初始實現,以便團隊可以了解交付、部署和維運的作業方式。 框架的工具和服務的最初實施和選擇通常不是長期計劃的。 例如,我們可能打算使用功能齊全的監控解決方案,但實際卻使用CSP現有的更多基本服務來建立運行骨架。
Refactoring
重構涉及更改系統或系統組件的設計,但不更改其行為。 重構通常是為了改變行為的變更鋪路。 重構可能會提高代碼的清晰度,以便更容易變更,或者可能會重新組織代碼,使其與計劃的更改保持一致。
重構的範例
ShopSpinner 團隊決定將其目前堆疊分解為多個堆疊和堆疊實例。 其計劃的實作包括一個用於託管其 Web 伺服器的容器叢集的堆疊實例,以及另一個用於共用網路結構的堆疊實例。 團隊還將為每個服務提供一對堆疊,一個用於應用程式伺服器及其相關網路,另一個用於該服務的資料庫實例(如下圖)。
團隊成員也希望更換他們的容器叢集產品,從他們自己部署到VM上的 K8S叢集遷移到使用CSP提供的Containers as a Service。團隊決定逐步實施其分解的架構。 第一步是將容器叢集提取到自己的堆疊中,然後替換堆疊內的容器產品(如下圖)。
該計劃是使用重構來實現變更的範例。 當容器叢集解決方案被隔離到自己的堆疊中時,比它是屬於其他基礎設施的更大堆疊的一部分時,更改容器叢集解決方案會更容易。 當團隊成員將叢集提取到自己的堆疊中時,他們可以為系統的其餘部分定義其整合點。 他們可以編寫測試和其他驗證,以保持分離和整合的乾淨度。 這樣做可以讓團隊相信他們可以安全地更改叢集堆疊的內容。
將不完整的變更推向生產
如何透過一系列小的增量變更來對生產系統產生重大變更,同時保持服務正常運作? 其中一些小量變更本身可能沒有用。 在整組變更完成之前刪除現有功能可能不切實際。
第上述的「重構範例」顯示了兩個增量步驟,將容器叢集從一個堆疊提取到自己的堆疊中,然後取代新堆疊中的叢集解決方案。 每個步驟都很大,因此可能會作為一系列較小的代碼推送來實現。
然而,許多較小量變更會使叢集本身無法使用。 因此,需要找到方法來進行這些較小的變更,同時保留現有的代碼和功能。 根據具體情況,可以使用不同的技術。
Parallel Instances
叢集替換範例的第二步驟從自己的堆疊中的原始容器解決方案開始轉換到新的容器解決方案(如下圖)。
現有的解決方案是一個名為 KubeCan 的打包 Kubernetes 發行版。 該團隊正在改用 FKS,這是其雲端平台提供的託管叢集服務。
一步一步把 KubeCan 叢集變成 FKS 叢集是不切實際的。 但團隊可以同時運行兩個叢集。 有幾種不同的方法可以與原始堆疊的單一實例同時運行兩個不同的容器堆疊。一種選擇是為主堆疊設定一個參數,以選擇要與哪個叢集堆疊整合(如下圖)。
使用此選項,將啟用其中一個堆疊並將處理即時工作負載。 第二個堆疊已停用但仍然存在。 該團隊可以在完全運行的環境中測試第二個堆疊,開發其交付流水線和測試套件,並將其與每個環境中基礎設施的其他部分整合。另一種選擇是將兩個堆疊與主堆疊整合(如下圖)。
透過這種安排,可以將部分工作負載指派給每個叢集堆疊。 透過不同的方式劃分工作負載:
Workload percentage
將部分工作負載定向到每個堆疊。 通常,舊堆疊首先處理大部分負載,新堆疊只佔一小部分來評估其運作情況。 隨著新堆疊的加入,可以隨著時間的推移增加負載。 在新堆疊成功管理 100% 的負載並且所有人都準備就緒後,就可以停用舊堆疊。 此選項假設新堆疊具有舊堆疊的所有功能,且跨堆疊拆分資料或訊息傳遞不存在任何問題。
Service migration
將服務一項一項遷移到新叢集上。 主堆疊中的工作負載(例如網路連線或訊息)被導向到相關服務正在其上執行的任何堆疊實例。 當需要修改服務應用程式以將其移至新堆疊時,此選項特別有效。 它通常需要更複雜的整合,甚至可能需要新舊叢集堆疊之間的整合。 這種複雜性對於遷移複雜的服務組合來說可能是合理的。
User partitioning
在某些情況下,不同的使用者群組會被定向到不同的堆疊實作。 測試人員和內部使用者通常是第一組。 他們可以在讓「真正的」客戶冒險之前進行探索性測試並運用新系統。 在某些情況下,可以透過向選擇加入 alpha 測試或預覽服務的客戶授予存取權限來遵循這一點。 當新堆疊上執行的服務發生使用者會注意到的變更時,這些情況更有意義。
有條件或同時地運行系統的新舊部分是一種抽象分支。 逐步將部分工作負載轉移到系統的新部分是金絲雀發佈。 暗啟動(Dark launching)是指將新的系統功能投入生產,但不將其暴露於生產工作負載,以便團隊可以對其進行測試。
向下相容的轉換
雖然某些變更可能需要與舊組件並行建置和運行新組件直至完成,但我們可以在組件內進行許多變更,而不會影響使用者或消費者。
即使新增或變更為"consumer components"提供的內容,通常也可以新增新的整合點,同時保持現有整合點不變。 使用者可以轉而自行使用新的整合點
。 例如,ShopSpinner 團隊計畫更改其shared-networking-stack,從單一 VLAN 遷移到三個 VLAN(如下圖)。
Consumer stacks(包括應用程式基礎設施堆疊)使用Iac part 17一文中「探索跨堆疊的依賴關係」中描述的發現方法之一與網路堆疊管理的單一VLAN 整合。shared-networking-stack代碼為其consumer stacks匯出VLAN identifier來探索:
vlans:
- main_vlan
address_range: 10.2.0.0/8
export:
- main_vlan: main_vlan.id
新版本的shared-networking-stack建立了三個 VLAN 並以新名稱匯出它們的identifiers。 它還使用舊identifiers導出 VLAN identifiers之一:
vlans:
- appserver_vlan_A
address_range: 10.4.0.0/16
- appserver_vlan_B
address_range: 10.6.0.0/16
- appserver_vlan_C
address_range: 10.8.0.0/16
export:
- appserver_vlan_A: appserver_vlan_A.id
- appserver_vlan_B: appserver_vlan_B.id
- appserver_vlan_C: appserver_vlan_C.id
# Deprecated
- main_vlan: appserver_vlan_A.id
透過保留舊identifier,修改後的網路堆疊仍然適用於consumer基礎設施代碼。 應修改consumer代碼以使用新identifier,並且一旦對舊identifier的所有依賴關係都消失,就可以將其從網路堆疊代碼中刪除。
功能切換
對組件進行變更時,通常需要繼續使用現有的架構,直到完成變更。 有些人在原始碼管理中對代碼進行分支,在一個分支中處理新的變更並在生產中使用舊分支。 這種方法的問題包括:
- 需要額外的工作來確保對組件其他區域的變更(例如錯誤修復)合併到兩個分支。
- 需要付出精力和資源來確保兩個分支持續測試和部署。 另一方面,一個分支中的作業測試則較不嚴格,從而增加了出現錯誤和稍後重工的機會。
- 一旦變更準備就緒,更改生產實例更像是一次「大霹靂式」操作,失敗的風險更高。
在不分支的情況下對主代碼庫進行變更會更有效。 可以使用功能切換來切換不同環境的代碼實作。 在某些環境中切換到新代碼來測試,並在生產線環境中切換到現有程式碼。 使用堆疊配置參數來指定代碼的哪一部分應用於特定實例。
一旦 ShopSpinner 團隊完成將 VLAN 新增至其shared-networking-stack(如前所述),團隊需要變更應用程式基礎架構堆疊以使用新的 VLAN。 團隊成員會發現這並不像他們最初想像的那麼簡單。
應用程式堆疊定義特定於應用程式的網路路由、負載平衡器 VIP 和防火牆規則。 當應用程式伺服器跨 VLAN 託管而不是在單一 VLAN 中託管時,這些情況會更加複雜。
團隊成員需要幾天的時間來實現此變更的代碼和測試。 這還不夠,他們覺得需要建立一個單獨的堆疊,如前面的「平行實例」所述。但他們熱衷於在作業時將增量變更推送到儲存庫,以便獲得持續的回饋來自測試,包括系統整合測試。
團隊決定在應用程式基礎設施堆疊中新增一個配置參數,該參數根據是應使用單一 VLAN 或多個 VLAN 來選擇堆疊代碼的不同部分。
這個堆疊原始碼片段使用三個變數(appserver_A_vlan、appserver_B_vlan 和 appserver_C_vlan)來指定要指派給每個應用程式伺服器的 VLAN。 其中每個值的設定都不同,取決於功能切換參數的值,toggle_use_multiple_vlans:
input_parameters:
name: toggle_use_multiple_vlans
default: false
variables:
- name: appserver_A_vlan
value:
$IF(${toggle_use_multiple_vlans} appserver_vlan_A ELSE main_vlan)
- name: appserver_B_vlan
value:
$IF(${toggle_use_multiple_vlans} appserver_vlan_B ELSE main_vlan)
- name: appserver_C_vlan
value:
$IF(${toggle_use_multiple_vlans} appserver_vlan_C ELSE main_vlan)
virtual_machine:
name: appserver-${SERVICE}-A
memory: 8GB
address_block: ${appserver_A_vlan}
virtual_machine:
name: appserver-${SERVICE}-B
memory: 8GB
address_block: ${appserver_B_vlan}
virtual_machine:
name: appserver-${SERVICE}-C
memory: 8GB
address_block: ${appserver_C_vlan}
如果toggle_use_multiple_vlans 切換設定為 false,則 appserver_X_vlan 參數全部設定為使用舊的 VLAN identifier 的main_vlan。 如果切換true,則每個變數都會設定為新的 VLAN identifiers之一。
堆疊代碼的其他部分使用相同的切換參數,團隊在其中配置路由和其他棘手的元素。
改變線上基礎設施
以下這些技術和範例解釋如何變更基礎架構代碼。 變更正在運作的基礎設施實例可能會比較棘手,尤其是在變更其他基礎設施正在消耗的資源時。
例如,當ShopSpinner 團隊將變更套用至shared-networking-stack代碼,將單一VLAN 取代為三個VLAN 時,分配給第一個 VLAN 的其他堆疊資源會發生什麼情況(如下圖)?
套用網路代碼會破壞 main_vlan,其中包含三個伺服器實例。 在實際環境中,破壞這些伺服器或將它們與網路分離,將破壞它們提供的任何服務。
大多數基礎設施平台將拒絕破壞附加伺服器執行個體的網路結構,因此操作將會失敗。 如果套用的代碼變更已刪除或變更了其他資源,則該操作可能會對執行個體實作這些變更,從而使環境處於新舊版本堆疊代碼之間的中間狀態。 這幾乎永遠是一件壞事。
有幾種方法可以處理這種即時基礎設施變更。 一種方法是保留舊 VLAN main_vlan,並新增兩個新 VLAN appserver_vlan_B 和 appserver_vlan_C。
執行此操作後,將獲得三個 VLAN(如預期),但其中一個的名稱與其他 VLAN 不同。 保留現有 VLAN 可能會需要一些妥協,例如其 IP 位址範圍,我們可能會決定透過保持原始 VLAN 小於新 VLAN 。這種妥協是一種壞習慣,會導致系統和代碼不一致,繼而難以維護和debug。
我們可以使用其他技術來變更線上系統並保持乾淨、一致的狀態。 一是使用基礎設施手術來編輯基礎設施資源。 另一個是擴大和縮小基礎設施資源。
基礎設施手術
一些堆疊管理工具(例如 Terraform)可讓我們存取將基礎設施資源對應到代碼的資料結構。 這些與用於依賴性探索的堆疊資料查找模式中使用的資料結構相同。
一些(但不是全部)堆疊工具具有編輯其資料結構的選項。 我們可以利用此功能對線上基礎架構進行變更。
ShopSpinner 團隊可以使用其虛構的堆疊工具來編輯其堆疊資料結構。 團隊成員將使用它來變更其生產環境以使用三個新的 VLAN。 首先使用新版本的代碼建立共享網路堆疊的第二個實例(如下圖)。
這三個堆疊的實例(1.應用程式基礎設施堆疊實例 2.共享網路堆疊的新舊實例)都有一個資料結構,用於指示基礎設施平台中的哪些資源屬於該堆疊(如下圖)。
ShopSpinner 團隊會將 main_vlan 從舊堆疊實例的資料結構移到新堆疊實例的資料結構中。 然後團隊將使用它來替換 appserver_vlan_A。
基礎架構平台中的 VLAN 不會發生任何變化,伺服器執行個體也將完全不變。 這些變更完全是堆疊工具資料結構中的記帳練習。團隊執行 stack 工具指令將 main_vlan 從舊堆疊移至新堆疊實例:
$ stack datafile move-resource \
source-instance=shared-networking-stack-production-old \
source-resource=main_vlan \
destination-instance=shared-networking-stack-production-new
Success: Resource moved
下一步是刪除 appserver_vlan_A。 如何執行此操作取決於實際的堆疊管理工具。 虛構的 stack 指令恰好讓這個操作變得異常簡單。 執行下列命令會破壞基礎設施平台中的 VLAN 並將其從資料結構檔案中刪除:
$ stack datafile destroy-resource \
instance=shared-networking-stack-production-new \
resource=appserver_vlan_A
Success: Resource destroyed and removed from the datafile
請注意,團隊成員尚未從堆疊原始碼中刪除 appserver_vlan_A,因此如果他們現在將代碼套用到實例,它將重建它。 但他們不會那樣做。 相反,他們將運行命令來重命名從舊堆疊實例中移動的 main_vlan 資源:
$ stack datafile rename-resource \
instance=shared-networking-stack-production-new \
from=main_vlan \
to=appserver_vlan_A
Success: Resource renamed in the datafile
當團隊將共享網路堆疊代碼應用於新實例時,它不應該改變任何內容。 就其而言,代碼中的所有內容都存在於實例中。
請注意,在堆疊之間編輯和移動資源的能力完全取決於堆疊管理工具。 CSP提供的大多數工具(至少在撰寫本文時)都沒有公開編輯堆疊資料結構的功能。
手動編輯堆疊資料結構時很容易出錯,因此導致中斷的風險很高。 我們可以編寫一個腳本來實現命令並在上游環境中測試它。 但這些編輯不是冪等的。 它們假設一個特定的起始狀態,如果有不同,執行腳本可能是不可預測的。
查看堆疊資料結構對於除錯很有用,但我們應該避免編輯它們。 可以說,可能需要編輯結構來解決中斷問題。 但這些情況的壓力往往會導致犯錯的可能性更高。 不應定期編輯堆疊資料。 每當嘗試編輯結構時,團隊都應該進行無可指責的事後分析,以了解如何避免重複。
對線上基礎設施進行更改的更安全的方法是擴展和縮小。
擴展和縮小
基礎設施團隊使用擴展和縮小模式(也稱為Parallel Change)來更改介面而不破壞consumer。 這個想法是,改變提供者的介面涉及兩個步驟:改變提供者,然後改變consumer。 擴展和縮小模式將這些步驟解耦。
此模式的本質是先新增資源,同時保留現有資源,然後將consumer切換到新資源,最後刪除舊的未使用資源。 這些變更中的每一個都是使用流水線交付的,因此它經過了徹底的測試。
透過擴展和縮小進行變更類似於向下相容轉換。 該技術取代了舊資源並將舊介面重新指向新資源之一。 但是,將新代碼應用於正在運行的實例將嘗試銷毀舊資源,這可能會破壞附加到它的任何consumer或無法完成。 因此需要一些額外的步驟。
ShopSpinner 團隊使用擴充功能和縮小來更改 VLAN 的第一步是將新 VLAN 新增到共用網路堆疊,同時保留舊的 main_vlan:
vlans:
- main_vlan
address_range: 10.3.0.0/8
- appserver_vlan_A
address_range: 10.4.0.0/16
- appserver_vlan_B
address_range: 10.5.0.0/16
- appserver_vlan_C
address_range: 10.6.0.0/16
export:
- main_vlan: main_vlan.id
- appserver_vlan_A: appserver_vlan_A.id
- appserver_vlan_B: appserver_vlan_B.id
- appserver_vlan_C: appserver_vlan_C.id
與 parallel instances技術和基礎設施手術不同,ShopSpinner 團隊不會添加堆疊的第二個實例,而僅更改現有實例。
套用此代碼後,現有的consumer實例不受影響 — — 它們仍然連接到 main_vlan。 團隊可以為新 VLAN 新增資源,也可以對consumer進行變更以進行切換。
如何切換consumer resource以使用新的資源取決於特定的基礎設施和平台。 在某些情況下,可以更新資源的定義以將其附加到新的提供者介面。 在其他情況下,我們可能需要銷毀並重建資源。
ShopSpinner 團隊無法將現有虛擬伺服器執行個體重新指派給新的 VLAN。 但是,團隊可以使用擴充和縮小模式來替換伺服器。 應用程式基礎設施堆疊代碼使用靜態 IP 位址定義每個伺服器,該位址將流量路由到伺服器:
virtual_machine:
name: appserver-${SERVICE}-A
memory: 8GB
vlan: external_stack.shared_network_stack.main_vlan
static_ip:
name: address-${SERVICE}-A
attach: virtual_machine.appserver-${SERVICE}-A
團隊的第一步是新增附加到新 VLAN 的新伺服器實例:
virtual_machine:
name: appserver-${SERVICE}-A2
memory: 8GB
vlan: external_stack.shared_network_stack.appserver_vlan_A
virtual_machine:
name: appserver-${SERVICE}-A
memory: 8GB
vlan: external_stack.shared_network_stack.main_vlan
static_ip:
name: address-${SERVICE}-A
attach: virtual_machine.appserver-${SERVICE}-A
此代碼中的第一個 virtual_machine 語法建立一個名為 appserver-${SERVICE}-A2 的新伺服器實例。 該團隊的流水線將這種變更傳遞到每個環境。 儘管團隊可以添加一些自動化測試來證明它運作正常,但此時尚未使用新的伺服器實例。
該團隊的下一步是將使用者流量切換到新的伺服器實例。 團隊對代碼進行了另一處更改,修改了 static_ip 語法:
virtual_machine:
name: appserver-${SERVICE}-A2
memory: 8GB
vlan: external_stack.shared_network_stack.appserver_vlan_A
virtual_machine:
name: appserver-${SERVICE}-A
memory: 8GB
vlan: external_stack.shared_network_stack.main_vlan
static_ip:
name: address-${SERVICE}-A
attach: virtual_machine.appserver-${SERVICE}-A2
透過流水線推送此變更會使新伺服器處於活動狀態,並停止到舊伺服器的流量。 團隊可以確保一切正常,並在出現問題時容易rollback變更以恢復舊伺服器。
一旦團隊讓新伺服器正常運作,就可以從堆疊代碼中刪除舊伺服器:
virtual_machine:
name: appserver-${SERVICE}-A2
memory: 8GB
vlan: external_stack.shared_network_stack.appserver_vlan_A
static_ip:
name: address-${SERVICE}-A
attach: virtual_machine.appserver-${SERVICE}-A2
一旦此變更通過流水線push並套用於所有環境,應用程式基礎設施堆疊將不再依賴共用網路堆疊中的 main_vlan。 所有consumer基礎設施都已更改後,ShopSpinner 團隊可以從提供者堆疊程式碼中刪除 main_vlan:
vlans:
- appserver_vlan_A
address_range: 10.4.0.0/16
- appserver_vlan_B
address_range: 10.5.0.0/16
- appserver_vlan_C
address_range: 10.6.0.0/16
export:
- appserver_vlan_A: appserver_vlan_A.id
- appserver_vlan_B: appserver_vlan_B.id
- appserver_vlan_C: appserver_vlan_C.id
VLAN變更完成,main_vlan的最後殘餘已被一掃而空。
零停機變更
本文所描述的許多技術解釋了如何逐步實施變更。 理想情況下,我們希望將變更套用到現有基礎架構而不中斷其提供的服務。 某些變更將不可避免地涉及破壞資源,或至少以可能中斷服務的方式更改資源。 有一些常見的技術可以處理這些情況。
Blue-green變更是一種建立新實例、將流量切換到新實例,然後刪除舊實例。 這在概念上類似於擴展和縮小,後者在組件實例(例如堆疊)中添加和刪除資源。 它是實現immutable infrastructure的關鍵技術。
Blue-green變更需要一種機制來處理工作負載從一個實例到另一個實例的切換,例如網路流量的負載平衡器。 複雜的實作允許「耗盡」工作負載,將新工作指派給新實例,並等待舊實例上的所有工作完成後再銷毀它。 一些自動化伺服器叢集和應用程式叢集解決方案將此作為一項功能提供,例如,支援對叢集中的實例進行「rolling upgrades」。
Blue-green是透過維護兩個環境使用靜態基礎設施來實現的。 一個環境在任何時間點都處於活動狀態,另一個環境已準備好採用下一個版本。 藍色和綠色的名稱強調了這些是輪流存在的平等環境,而不是主要和次要環境。
連續性
IaC Part 1討論了管理基礎設施的傳統「地端機房時代」方法與現代「雲端時代」方法之間的對比。 當我們更多地使用實體設備並手動管理它們時,進行變更的成本很高。
犯錯的代價也很高。 當配置一台沒有足夠RAM的新伺服器時,花了一周或更長時間訂購更多RAM,然後拿到資料中心,關閉伺服器電源並將其從機架中拉出,打開它並添加額外的RAM,然後重新架設並再次啟動伺服器。
使用雲端時代實踐進行更改的成本要低得多,糾正錯誤所需的成本和時間也低得多。 如果配置的伺服器沒有足夠的RAM,只需幾分鐘即可透過編輯檔案並將其套用到虛擬伺服器來修正它。
地端機房時代的連續性方法強調預防。 他們透過犧牲變化速度和頻率來優化 MTBF(平均故障間隔時間)。 雲端時代方法針對 MTTR(平均恢復時間)進行最佳化。 儘管甚至一些現代方法的愛好者也會陷入這樣的陷阱,認為關注MTTR 就意味著犧牲MTBF,但這是不正確的,正如IaC part1一文中的“反對:“我們必須在速度和品質之間做出選擇」中所解釋的那樣。四個關鍵指標(變更的速度和頻率、MTTR 和變更失敗率)帶來了強大的 MTBF。 重點不是“快速行動並打破現狀”,而是“快速行動並修復現狀”。
實現現代化基礎設施的連續性有幾個要素。 預防是雲端時代變革管理實踐的重點,這一點至關重要,但雲端基礎設施和自動化可以使用更有效的敏捷工程實踐來減少錯誤。 此外,我們可以利用新技術和實踐來恢復和重建系統,以實現比以前想像的更高水平的連續性。 透過不斷運用提供變更和復原系統的機制,我們可以確保可靠性並為各種災難做好準備。
透過防止錯誤實現連續性
如前所述,地端機房時代的治理變革方法主要是預防性的。 由於糾正錯誤的成本很高,組織在預防錯誤方面投入了大量資金。 由於變更主要是手動進行的,因此預防措施包括限制誰可以進行變更。 技術人員需要詳細規劃和設計變更,其他人則詳盡地審查和討論每個變更。 這個想法是讓更多的人花更多的時間提前考慮變更會發現錯誤。
這種方法的一個問題是設計文件和實作之間的差距。 圖表中看似簡單的事情在現實中可能很複雜。 人們會犯錯,尤其是在進行大量、不頻繁的升級時。 結果是,傳統的低頻率、高度規劃的大批量變更操作的失敗率很高,而且通常恢復時間很長。
本系列文章中所描述的實踐和模式旨在防止錯誤,同時不犧牲變更的頻率和速度。 定義為代碼的變更比任何圖表或設計文件都可以更好地代表其實現。 在作業中不斷整合、應用和測試變更,證明它們已做好生產準備。 使用流水線來測試和交付變更可確保不會跳過步驟,並強制跨環境的一致性。 這降低了生產環境失敗的可能性。
敏捷軟體開發和IaC的核心見解是轉變對變更的心態。 我們可以透過經常進行變更來防止錯誤,而不是害怕變更並盡可能少地進行變更。 更好地進行變更的唯一方法是經常進行變更,並不斷改進系統和流程。
另一個重要的見解是,隨著系統變得更加複雜,我們複製和準確測試代碼在生產環境中的行為方式的能力會下降。 我們需要了解在生產前可以測試什麼、不能測試什麼,以及如何透過提高生產系統的可見度來降低風險。
透過快速恢復實現連續性
本文到目前為止所描述的做法可以減少停機時間。 限制變更的規模、增量地進行變更以及在生產之前測試變更可以降低變更失敗率。 但假設錯誤是可以完全避免是不明智的,因此我們還需要能夠快速容易地恢復。
本系列文章中所提倡的實踐使重建系統的任何部分變得容易。 系統由鬆散耦合的組件組成,每個組件都定義為冪等代碼。 可以透過重新套用其代碼修復或銷毀並重建任何組件實例。 如果重建組件,則需要確保組件上託管的資料的連續性。
在某些情況下,平台或服務可以自動重建失敗的基礎架構。 當健康檢查失敗時,基礎設施平台或application runtime會銷毀並重建各個組件。 持續將代碼套用到實例會自動恢復與代碼的任何偏差。 我們可以手動觸發流水線階段以將代碼重新套用到損壞的組件。
在其他故障情況下,這些系統可能不會自動修復問題。 運算實例可能會發生故障,但仍能通過運行狀況檢查。 基礎設施組件可能會停止正常運作,但仍與代碼定義相匹配,因此重新套用代碼並沒有幫助。
這些場景需要某種額外的操作來替換故障的組件。 可以標記一個組件,以便自動化系統認為它出現故障,並銷毀和替換它。 或者,如果恢復使用重新套用代碼的系統,可能需要自行銷毀組件並允許系統建置新實例。
對於任何需要有人採取行動的故障場景,我們應該確保擁有易於執行的工具、腳本或其他機制。 技術人員不需要遵循一系列步驟; 例如,在銷毀實例之前備份資料。 相反,他們應該調用一個執行所有必需步驟的操作。 目標是,在緊急情況下,無需考慮如何正確恢復系統。
持續災難復原
地端機房時代基礎設施管理方法將災難復原視為不尋常的事件。 從靜態硬體故障中恢復通常需要將工作負載轉移到一組單獨的備用硬體上。
許多組織很少測試其恢復操作 — — 最多每隔幾個月測試一次,在某些情況下每年測試一次。 很多組織很少測試他們的故障轉移流程。 我們的假設是,如果需要,團隊將研究如何使其備份系統運行,即使需要幾天時間。
持續災難復原利用與配置和更改基礎架構相同的流程和工具。 如前所述,可以應用基礎架構代碼來重建失敗的基礎架構,也許可以添加一些自動化功能以避免資料遺失。
雲端時代基礎設施的原則之一是假設系統不可靠(原則:假設系統不可靠)。 我們無法將軟體安裝到虛擬機器並期望它可以在其中運行任意長時間。 CSP可能會移動、銷毀或更換機器或其主機系統以進行維護、安全性修補程式或升級。 因此,需要準備好在需要時更換伺服器。
將災難復原視為正常操作的延伸比將其視為異常更可靠。 團隊在處理基礎設施代碼變更和系統更新時每天多次練習復原流程和工具。 如果有人對腳本或其他代碼進行了更改,從而破壞了配置或導致更新時資料遺失,則通常會在流水線測試階段失敗,因此可以快速修復它。
混沌工程
Netflix 是持續災難復原和雲端時代基礎設施管理的先驅。 其Chaos Monkey和Simian Army將持續災難復原的概念更進一步,透過向生產系統注入錯誤來證明其係統連續性機制的有效性。 這演變成了混沌工程領域,“對系統進行實驗以建立對系統能力的信心的學科。”
需要明確的是,混沌工程並不是不負責任地造成生產服務中斷。 從業者會嘗試他們的系統預期能夠處理的特定故障場景。 這些是重要的生產測試,可證明檢測和恢復機制正常運作。 目的是當系統的某些變化產生干擾這些機制的副作用時獲得快速回饋。
故障規劃
故障是不可避免的。 雖然我們可以而且應該採取措施來降低它們的可能性,但還是需要採取措施來降低它們的危害並更容易處理。
團隊舉辦故障場景對應研討會,集思廣益可能發生的故障類型,然後規劃緩解措施。 建立每個場景的可能性和影響圖,建立解決這些場景的行動清單,然後將這些內容適當地優先考慮到團隊的積壓(backlog)作業中。 對於任何特定的故障場景,有幾個條件需要探索:
原因及預防
哪些情況可能會導致這種失敗,可以採取哪些措施來降低這種失敗的可能性? 例如,當使用量激增時,伺服器可能會耗盡磁碟空間。 透過分析磁碟使用模式並擴展磁碟大小來解決此問題,以便有足夠的空間來滿足更高的使用等級。 還可以實施自動化機制來持續分析使用等級並做出預測,以便在模式變更時可以搶先新增磁碟空間。 進一步的步驟是隨著使用量的增加自動調整磁碟容量。
故障模式
發生故障時會發生什麼事? 在沒有人為干預的情況下,可以做什麼來減少後果? 例如,如果特定伺服器的磁碟空間不足,則在其上執行的應用程式可能會接受交易但無法記錄它們。 這可能非常有害,因此可以修改應用程式以停止接受交易(如果它無法將交易記錄到磁碟)。 在許多情況下,團隊實際上並不知道發生特定錯誤時會發生什麼。 理想情況下,故障模式可以使系統保持完全運作。 例如,當應用程式停止回應時,您的負載平衡器可能會停止將流量定向到該應用程式。
偵測
當故障發生時是如何偵測? 可以做什麼來更快地甚至提前偵測到它? 當應用程式崩潰並且客戶打電話給大老闆抱怨時,我們可能會檢測到磁碟空間不足。 最好在應用程式崩潰時收到通知。 最好在磁碟空間實際填滿之前,在磁碟空間不足時收到通知。
修正
需要採取哪些步驟才能從故障中復原? 在某些情況下,如前所述,系統可能會透過銷毀和重建無回應的應用程式實例來自動修正這種情況。 其他人需要幾個步驟來修復和重新啟動服務。
如果系統自動處理故障情境(例如重新啟動無回應的運算執行個體),則應考慮更深層的故障情境。 為什麼實例首先變得沒有回應? 我們將如何偵測並修正根本問題? 我們不需要幾天的時間就能意識到應用程式實例每隔幾分鐘就會被回收一次。
故障規劃是一個持續的過程。 每當系統發生事件(包括在開發或測試環境中)時,團隊都應該考慮是否需要定義和規劃新的故障情境。
應該實施檢查來證明故障場景。 例如,如果當伺服器磁碟空間不足時,應用程式將停止接受交易、自動新增伺服器實例並向團隊發出告警,則應該有一個自動化測試來演練此場景。 可以在流水線階段對此進行測試或使用混沌實驗。
不斷變化的系統中的資料連續性
許多用於部署軟體和管理基礎架構的雲端時代實踐和技術都樂於推薦隨意破壞和擴展資源,而只需揮手即可解決資料問題。 如果您認為 DevOps 信仰者認為整個資料概念是對地端機房時代的倒退,這是可以理解的 — — 畢竟,真正的十二因子應用程式是無狀態的。 現實世界中的大多數系統都涉及資料,人們會對資料產生情感上的依戀。
當增量地變更系統時,資料可能會帶來挑戰。執行儲存基礎架構的並行實例(parallel instances)可能會造成不一致,甚至損壞資料。 許多增量部署變更的方法都依賴rollback變更的能力,而這對於資料架構變更來說可能是辦不到的。
動態新增、刪除和重建託管資料的基礎架構資源尤其具有挑戰性。 但是,根據具體情況,有一些方法可以對其進行管理。 一些方法包括鎖定、隔離、複製和重新載入。
鎖定
某些基礎設施平台和堆疊管理工具可讓我們鎖定特定資源,這樣它們就不會被會破壞它們的命令刪除。 例如為儲存組件指定此設置,則該工具將拒絕對此組件套用變更,從而允許團隊成員手動進行。
然而,這存在一些問題。 在某些情況下,如果對受保護的資源套用變更,該工具可能會使堆疊處於部分修改的狀態,這可能會導致整個服務停機。
但根本問題是,保護某些資源免受自動變更的影響會鼓勵手動變更。 手工作業會導致人為錯誤。 最好找到一種方法來自動化流程,從而安全地更改基礎架構。
隔離
可以透過將託管資料的資源與系統的其他部分分開來隔離資料; 例如,透過製作單獨的堆疊。 可以透過分離並重新附加其磁碟區來銷毀和重建運算實例。
將資料保存在資料庫中可以提供更大的靈活性,可能允許我們添加多個運算實例。 但仍然需要為託管資料的堆疊制定資料連續性策略,但這縮小了問題的範圍。 可以使用託管 DBaaS 服務完全卸除資料連續性。
複製(Replicate)
根據資料及其管理方式,我們也許能夠跨基礎架構的多個實例複製它。 一個典型的例子是跨節點複製資料的分散式資料庫叢集。
透過正確的複製策略,資料可以從叢集中的其他節點重新載入到新重建的節點。 如果遺失太多節點,這種策略就會失敗,這種情況可能會在重大託管中斷時發生。 因此,這種方法可以作為第一道防線,同時也需要另一種機制來應對更困難的故障場景。
重新載入(Reload)
最著名的資料連續性解決方案是從更可靠的儲存基礎架構備份和還原資料。 當重建託管資料的基礎架構時,首先要備份資料。 建立新實例後,將資料重新載入到新實例。 也可以定期進行備份,並且可以在復原場景中重新載入備份,儘管將遺失備份和復原之間發生的任何資料變更。 透過將streaming data changesn送到備份(例如寫入資料庫交易日誌),可以最大限度地減少甚至消除這種情況。
雲端平台提供不同的儲存服務,具有不同的可靠性等級。 例如,AWS S3等物件儲存服務通常比AWS EBS等區塊儲存服務對資料的持久性有更強的保證。 因此,可以透過將資料複製或串流傳輸到物件儲存磁碟區來實現備份。
我們不僅應該自動化備份資料的過程,還應該自動化恢復資料的過程。 基礎設施平台可能提供實現此目的的方法。 例如,可以在套用變更之前自動快照磁碟儲存磁碟區。 可以使用磁碟磁碟區快照來最佳化為資料庫叢集等系統新增節點的過程。 與其建立具有空儲存磁碟區的新資料庫節點,不如將其附加到另一個節點磁碟的複製可能會更快地同步並使節點連線。
「未經測試的備份與沒有備份相同」是IT產業的一句格言。 鑑於我們遵循IaC實踐,我們已經在對系統的各個方面使用自動化測試。 因此可以對備份執行相同的操作。 無論是否在生產中,都可以在流水線中或作為混沌實驗來練習備份復原過程。
混合資料連續性方法
最好的解決方案通常是隔離、複製和重新載入的組合。 隔離資料為更靈活地管理系統的其他部分創造了空間。 複製使資料在大部分時間都可用。 重新載入資料可以應對更極端的情況。
結論
現代雲端時代基礎設施管理實踐的倡議者常常忽略連續性。 保持系統可靠運作的最熟悉的方法是基於地端機房時代的前提,即進行變更是昂貴且有風險的。 這些方法往往會破壞雲端、敏捷和其他注重快速變化的方法的優勢。
本文已經解釋了如何利用雲端時代思維使系統變得更加可靠,不是儘管變化速度很快,而是因此而變得更加可靠。 我們可以利用現代基礎設施平台的動態特性,並嚴格關注來自敏捷工程實踐的測試和一致性。