基礎設施堆積積疊是一組作為單一單位管理的基礎設施資源集合。環境同樣是基礎設施資源的集合。那麼,堆積積疊與環境是否相同?本文將探討這兩個概念的關係與差異。

環境是圍繞特定目的(如支援測試階段或在特定地理區域提供服務)而組織的軟體和基礎設施資源集合。而堆積積疊則是定義和管理基礎設施資源集合的方法。我們使用堆積積疊或多個堆積積疊來實作環境。可以將環境實作為單一堆積積疊,也可以由多個堆積積疊組成。理論上甚至可以在一個堆積積疊中建立多個環境,但這並不是好的做法。

環境的本質

在資訊科技領域,「環境」是一個我們習以為常的概念,但在不同情境下可能有細微差異。在這篇文章中,環境是一組操作相關的基礎設施資源集合,這些資源支援特定活動,如測試或執行系統。通常,會存在多個環境,每個環境執行同一系統的例項。

有兩種典型的使用多環境的情境:一是支援交付流程,二是執行系統的多個生產例項。

交付環境

多環境的最常見使用案例是支援漸進式軟體發布流程——有時稱為「通往往生產的路徑」。應用程式的特定版本會依次佈署到每個環境,以支援不同的開發和測試活動,最終佈署到生產環境。

多重生產環境

也可能使用多個環境來執行系統在生產中的完整獨立副本。這樣做的原因包括:

故障容錯:如果一個環境失敗,其他環境可以繼續提供服務。這可能涉及容錯移轉流程,將負載從失敗的環境轉移出去。也可以在環境內部實作故障容錯,例如使用伺服器叢集。執行額外的環境會複製所有基礎設施,建立更高程度的故障容錯,但成本也更高。

可擴充套件性:可以將工作負載分散到多個環境中。人們通常按地理位置進行劃分,為每個區域設定單獨的環境。多環境可同時用於實作可擴充套件性和故障容錯。如果一個區域發生故障,負載會轉移到另一個區域的環境,直到故障得到修復。

隔離:可能為不同的使用者群執行應用程式或服務的多個例項,例如不同的客戶。在不同環境中執行這些例項可以加強隔離。更強的隔離有助於滿足法律或合規要求,並增強客戶信心。

案例:ShopSpinner的區域環境

ShopSpinner為每個電子商務客戶執行單獨的應用伺服器。隨著擴充套件到北美、歐洲和南亞支援客戶,它決定為每個區域建立單獨的環境。

使用完全分離的環境,而不是在各區域間分享單一環境,幫助ShopSpinner確保遵守不同區域關於儲存客戶資料的不同法規。此外,如果需要進行涉及停機的變更,可以在不同時間在每個區域進行。這使得將停機時間與不同時區對齊變得更容易。

後來,ShopSpinner與一家名為The Medicine Barn的製藥連鎖店簽訂了合約。The Medicine Barn出於監管原因需要將其客戶資料與其他公司分開託管。因此,ShopSpinner團隊提出以比分享環境更高的成本執行一個完全獨立的專用環境。

環境、一致性和設定

由於多個環境旨在執行同一系統的例項,每個環境中的基礎設施應保持一致。環境間的一致性是基礎設施即程式碼的主要驅動因素之一。

環境之間的差異會造成行為不一致的風險。在一個環境中測試軟體可能無法發現在另一個環境中出現的問題。軟體甚至可能在某些環境中成功佈署,而在其他環境中失敗。

另一方面,環境之間通常需要一些特定差異。測試環境可能比生產環境小。不同人在不同環境中可能有不同的許可權。不同客戶的環境可能有不同的功能和特性。至少,名稱和ID可能不同(appserver-test、appserver-stage、appserver-prod)。因此,至少需要設定環境的某些方面。

環境的一個關鍵考量是測試和交付策略。當相同的基礎設施程式碼應用於每個環境時,在一個環境中測試它往往會增強它在其他環境中正常工作的信心。但如果基礎設施在各例項間差異很大,就無法獲得這種信心。

可以透過使用不同的設定值測試基礎設施程式碼來提高信心。但是,測試許多不同的值可能不切實際。在這些情況下,可能需要額外的驗證,如佈署後測試或監控生產環境。

構建環境的模式

環境是基礎設施元素的概念集合,而堆積積疊是基礎設施元素的具體集合。堆積積疊專案是用來建立一個或多個堆積積疊例項的原始碼。那麼,應該如何使用堆積積疊專案和例項來實作環境呢?

以下將描述兩種反模式和一種模式,用於使用基礎設施堆積積疊實作環境。每種模式描述了使用基礎設施堆積積疊定義多個環境的方法。

反模式:多環境堆積積疊

多環境堆積積疊將多個環境的基礎設施定義和管理為單一堆積積疊例項。

例如,如果有三個用於測試和執行應用程式的環境,單一堆積積疊專案包含所有三個環境的程式碼。

動機

許多人在學習新的堆積積疊工具時建立這種結構,因為將新環境增加到現有專案中似乎很自然。

後果

當執行工具更新堆積積疊例項時,潛在變更的範圍是堆積積疊中的所有內容。如果程式碼中有錯誤或衝突,例項中的所有內容都很脆弱。

當生產環境與另一個環境在同一堆積積疊例項中時,更改另一個環境會有導致生產問題的風險。程式碼錯誤、意外依賴關係,甚至工具中的錯誤都可能在只打算更改測試環境時破壞生產環境。

相關模式

可以透過將環境分成單獨的堆積積疊來限制變更的影響範圍。一種明顯的方法是複製貼上環境,每個環境都是一個單獨的堆積積疊專案,儘管這被視為反模式。

更好的方法是可重用堆積積疊模式。使用單一專案定義環境的通用結構,然後用於管理每個環境的單獨堆積積疊例項。雖然這涉及使用單一專案,但該專案一次只應用於一個環境例項。因此,變更的影響範圍僅限於該環境。

反模式:複製貼上環境

複製貼上環境反模式為每個基礎設施堆積積疊例項使用單獨的堆積積疊原始碼專案。

在名為test、staging和production的三個環境的例子中,每個環境都有單獨的基礎設施專案。透過編輯一個環境中的程式碼然後依次將更改複製到每個其他環境中來進行更改。

動機

複製貼上環境是維護多個環境的直觀方法。它們避免了多頭堆積積疊反模式的影響範圍問題。還可以輕鬆自定義每個堆積積疊例項。

適用性

如果想要維護和更改不同的例項,與不擔心程式碼重複或一致性,複製貼上環境可能是合適的。

後果

維護多個複製貼上環境可能具有挑戰性。當想要進行程式碼更改時,需要將其複製到每個專案。可能需要單獨測試每個例項,因為更改可能在一個環境中有效,但在另一個環境中無效。

複製貼上環境常遭受設定漂移。將複製貼上環境用於交付環境會降低佈署過程的可靠性和測試的有效性,這是由於從一個環境到下一個環境的不一致性造成的。

複製貼上環境在首次設定時可能是一致的,但隨著時間的推移,變化會逐漸出現。

實作

透過將一個堆積積疊例項的專案程式碼複製到新專案中來建立複製貼上環境。然後編輯程式碼以針對新例項進行自定義。當對一個堆積積疊進行更改時,需要將其複製並貼上到所有其他堆積積疊專案中,同時保持每個專案中的自定義。

相關模式

環境分支可被視為複製貼上環境的一種形式。每個分支都有程式碼的副本,人們透過合併在分支之間複製程式碼。持續應用程式碼可能避免複製貼上的陷阱,因為它保證程式碼從一個環境到下一個環境不會被修改。將程式碼編輯作為合併到環境分支的一部分會產生複製貼上反模式的危害。

包裝器堆積積疊模式也類別似於複製貼上環境。包裝器堆積積疊為每個環境使用單獨的堆積積疊專案來設定引數。但堆積積疊的程式碼是在堆積積疊元件中實作的,例如可重用的模組程式碼。該程式碼本身不會為每個環境複製和貼上,而是像可重用堆積積疊一樣提升。但是,如果人們在包裝器堆積積疊專案中增加的不僅是基本堆積積疊例項引數,它可能會退化為複製貼上環境反模式。

在堆積積疊例項旨在表示相同堆積積疊的情況下,可重用堆積積疊模式通常更合適。

模式:可重用堆積積疊

可重用堆積積疊是用於建立堆積積疊的多個例項的基礎設施原始碼專案。

動機

建立可重用堆積積疊是為了維護多個一致的基礎設施例項。當對堆積積疊程式碼進行更改時,可以在一個例項中應用和測試它,然後使用相同的程式碼版本建立或更新多個額外的例項。希望以最少的儀式甚至自動地佈建堆積積疊的新例項。

例如,ShopSpinner團隊從每個使用應用伺服器的不同堆積積疊專案中提取了通用程式碼。團隊成員將通用程式碼放入每個堆積積疊專案使用的模組中。後來,他們意識到客戶應用程式的堆積積疊專案仍然非常相似。除了使用模組建立應用伺服器外,每個堆積積疊都有程式碼來建立資料函式庫以及每個客戶的專用日誌和報告服務。

跨多個客戶更改和測試此程式碼的更改變得麻煩,而與ShopSpinner每個月都在簽約新客戶。因此,團隊決定建立一個定義客戶應用程式堆積積疊的單一堆積積疊專案。該專案仍然使用分享的Java應用伺服器模組,其他一些應用程式(如Jira和GoCD)也是如此。但該專案還包含設定每個客戶基礎設施其餘部分的程式碼。

現在,當他們簽約新客戶時,團隊成員使用通用客戶堆積積疊專案建立新例項。當他們修復或改進專案程式碼中的某些內容時,會將其應用於測試例項以確保沒問題,然後逐一推廣到客戶例項。

適用性

可以將可重用堆積積疊用於交付環境或多個生產環境。當環境之間不需要太多變化時,此模式很有用。當環境需要大量自定義時,它的適用性較低。

後果

能夠從同一專案佈建和更新多個堆積積疊可增強可擴充套件性、可靠性和吞吐量。可以用更少的努力管理更多的例項,以更低的失敗風險進行更改,並更快地將更改推廣到更多系統。

通常需要為不同的例項設定堆積積疊的某些方面,即使只是命名方式。

在將更改應用於業務關鍵基礎設施之前,應該測試堆積積疊專案程式碼。

實作

將可重用堆積積疊建立為基礎設施堆積積疊專案,然後每次想要建立或更新堆積積疊例項時執行堆積積疊管理工具。使用堆積積疊工具命令的語法告訴它要建立或更新哪個例項。例如,使用Terraform,為每個例項指定不同的狀態檔案或工作區。使用CloudFormation,為每個例項傳遞唯一的堆積積疊ID。

以下範例命令使用名為stack的虛構命令從單一專案佈建兩個堆積積疊例項。該命令接受一個引數env,用於識別唯一例項:

> stack up env=test --source mystack/src
SUCCESS: stack 'test' created
> stack up env=staging --source mystack/src
SUCCESS: stack 'staging' created

通常,應該使用簡單引數來定義堆積積疊例項之間的差異——字元串、數字或在某些情況下的列表。此外,可重用堆積積疊建立的基礎設施在各例項之間不應有太大差異。

相關模式

可重用堆積積疊是對複製貼上環境反模式的改進,使得保持多個例項一致變得更容易。

包裝器堆積積疊模式使用堆積積疊元件定義可重用堆積積疊,但使用不同的堆積積疊專案為每個例項設定引數值。

使用多個堆積積疊構建環境

可重用堆積積疊模式描述了實作多環境的方法。前面我們描述了跨多個堆積積疊構建系統基礎設施的不同方式。有幾種方法可以實作堆積積疊,將環境和系統結構這兩個維度結合起來。

簡單的情況是將完整系統實作為單一堆積積疊。當佈建堆積積疊的例項時,就有了一個完整的環境。

但應該將較大的系統拆分為多個堆積積疊。例如,如果遵循服務堆積積疊模式,每個服務都有一個單獨的堆積積疊。

要建立多個環境,為每個環境佈建每個服務堆積積疊的例項。

使用以下命令可以用多個堆積積疊構建完整環境:

> stack up env=staging --source product_browse_stack/src
SUCCESS: stack 'product_browse-staging' created
> stack up env=staging --source product_search_stack/src
SUCCESS: stack 'product_search-staging' created
> stack up env=staging --source shopping_basket_stack/src
SUCCESS: stack 'shopping_basket-staging' created

在基礎設施即程式碼的世界中,理解環境與堆積積疊的關係至關重要。環境是圍繞特定目的組織的資源集合,而堆積積疊是管理這些資源的工具。透過選擇適當的模式,如可重用堆積積疊,可以有效地管理多個環境,確保一致性並減少維護負擔。

避免多環境堆積積疊和複製貼上環境這兩種反模式,可以降低風險並提高效率。對於較大的系統,將其拆分為多個服務堆積積疊,然後為每個環境佈建這些堆積積疊的例項,是一種靈活與可擴充套件的方法。

無論選擇哪種方法,關鍵是確保環境之間的一致性,同時允許必要的設定差異,以支援不同的使用案例,如測試、區域佈署或客戶隔離。

跨堆積積疊整合:有效管理多堆積積疊基礎設施的策略

在現代雲端架構中,隨著系統規模擴大,將基礎設施拆分為多個堆積積疊(stack)已成為管理複雜性的必要策略。本文將探討如何有效地整合和管理多堆積積疊環境,確保各堆積積疊間的協調運作,同時保持基礎設施的一致性與可靠性。

堆積積疊拆分的必要性與挑戰

當基礎設施規模擴大時,單一堆積積疊的管理變得越來越困難。拆分為多個堆積積疊可以帶來許多好處,包括更快的佈署速度、更精確的許可權控制,以及更好的故障隔離。然而,這種拆分也帶來了整合的挑戰。

多堆積積疊環境面臨的主要挑戰包括:

  1. 確保堆積積疊間的依賴關係正確管理
  2. 維持跨堆積積疊的設定一致性
  3. 在不同環境中保持堆積積疊例項的一致性
  4. 有效傳遞堆積積疊間的引數與輸出值

可重用堆積積疊模式的核心價值

可重用堆積積疊(Reusable Stack)應該是大多數需要管理大型基礎設施團隊的首選模式。堆積積疊作為測試和交付變更的單位,具有以下優勢:

  • 提供每個環境例項一致定義和建構的保證
  • 作為變更單位的完整性,增強了快速與頻繁交付變更的能力
  • 相較於模組,堆積積疊提供更全面的變更管理機制

堆積積疊例項設定的最佳實踐

設計原則:保持引數簡單

設定堆積積疊時,簡單性是關鍵。過度複雜的引數設計會導致難以理解的行為、測試困難,以及不可靠的交付。應遵循以下原則:

  • 優先使用簡單的引數型別,如字串、數字、列表和鍵值對映
  • 最小化堆積積疊可設定的引數量,避免定義「可能」需要的引數
  • 避免使用引數作為條件來建立基礎設施的重大差異

當遵循這些建議變得困難時,可能是重構堆積積疊程式碼的訊號,或許需要將其拆分為多個堆積積疊專案。

使用堆積積疊引數建立唯一識別符

當從同一專案建立多個堆積積疊例項時,可能會遇到資源識別符衝突的問題。例如:

server:
  id: appserver
  subnet_id: appserver-subnet

當嘗試建立第二個堆積積疊時,會因為識別符重複而失敗。解決方案是使用引數來避免這些衝突:

server:
  id: appserver-${environment}
  subnet_id: appserver-subnet-${environment}

這樣就可以無錯誤地建立多個堆積積疊例項。

堆積積疊設定的模式與反模式

反模式:手動堆積積疊引數

最直接的方法是在命令列手動輸入引數值:

stack up environment=production --source mystack/src

雖然這種方法簡單直接,適合學習和實驗階段,但存在明顯缺點:

  • 容易在命令列輸入時出錯
  • 難以記住每個環境的正確值
  • 不適合自動化應用基礎設施程式碼
  • 團隊成員難以保持一致的引數使用

模式:堆積積疊環境變數

這種模式涉及將引數值設定為堆積積疊工具使用的環境變數:

export STACK_ENVIRONMENT=test
export STACK_CLUSTER_MINIMUM=1
export STACK_CLUSTER_MAXIMUM=1

堆積積疊程式碼可以直接參照這些變數:

container_cluster: web_cluster-${ENV("STACK_ENVIRONMENT")}
min_size: ${ENV("STACK_CLUSTER_MINIMUM")}
max_size: ${ENV("STACK_CLUSTER_MAXIMUM")}

優點是大多數平台和工具都支援環境變數,實作簡單。但缺點包括:

  • 需要額外的模式來設定值
  • 難以追蹤特定堆積積疊例項的設定值
  • 在環境變數中設定機密可能會將其暴露給同一系統上執行的其他程式

模式:指令碼化引數

指令碼化引數將引數值硬編碼到執行堆積積疊工具的指令碼中:

#!/bin/sh
case $1 in
  test)
    CLUSTER_MINIMUM=1
    CLUSTER_MAXIMUM=1
    ;;
  staging)
    CLUSTER_MINIMUM=2
    CLUSTER_MAXIMUM=3
    ;;
  production)
    CLUSTER_MINIMUM=2
    CLUSTER_MAXIMUM=6
    ;;
  *)
    echo "Unknown environment $1"
    exit 1
    ;;
esac

stack up \
  environment=$1 \
  cluster_minimum=${CLUSTER_MINIMUM} \
  cluster_maximum=${CLUSTER_MAXIMUM}

這種方法的優點是:

  • 簡單捕捉每個例項的值,避免手動引數的問題
  • 確保每個環境的值一致使用
  • 透過版本控制追蹤設定值的變更

但不適合儲存機密,需要實施單獨的模式來處理機密。

模式:堆積積疊設定檔案

堆積積疊設定檔案在單獨的檔案中管理每個例項的引數值:

├── src/
│   ├── cluster.infra
│   ├── host_servers.infra
│   └── networking.infra
├── environments/
│   ├── test.properties
│   ├── staging.properties
│   └── production.properties
└── test/

設定檔案內容可能如下:

env = staging
cluster_minimum = 2
cluster_maximum = 3

執行堆積積疊命令時傳遞相關引數檔案的路徑:

stack up --source ./src --config ./environments/staging.properties

這種方法的優點是:

  • 直觀與易於理解
  • 設定與堆積積疊程式碼分離
  • 易於追蹤歷史和稽核變更

但缺點包括:

  • 需要為每個新堆積積疊例項增加新的設定檔案
  • 可能為下游環境的設定變更增加摩擦
  • 不應包含機密

模式:包裝堆積積疊

包裝堆積積疊為每個例項使用單獨的基礎設施堆積積疊專案作為包裝,匯入所有堆積積疊例項分享的堆積積疊程式碼元件:

module:
  name: container_cluster_module
  version: 1.23
  parameters:
    env: test
    cluster_minimum: 1
    cluster_maximum: 1

這種方法的優點是:

  • 利用堆積積疊工具的模組功能或函式庫支援跨堆積積疊例項重用分享程式碼
  • 可以使用與定義基礎設施相同的語言編寫佈建和設定堆積積疊的邏輯

但缺點包括:

  • 元件在堆積積疊和元件中包含的程式碼之間增加了額外的複雜層
  • 可能誘使人們為每個例項增加自定義邏輯
  • 不能用於管理機密

模式:管道堆積積疊引數

在交付管道的設定中為每個例項定義值:

stage: apply-test-stack
input_artifacts: container_cluster_stack
commands:
  unpack ${input_artifacts}
  stack up --source ./src environment=test cluster_minimum=1 cluster_maximum=1
  stack test environment=test

這種方法的優點是:

  • 如果已經使用管道工具執行基礎設施堆積積疊工具,它提供了儲存和傳遞引數值的機制
  • 設定值與基礎設施程式碼分開儲存

但缺點包括:

  • 將設定值與交付過程耦合
  • 管道設定可能變得複雜與難以維護
  • 堆積積疊工具在管道外執行變得更加困難

模式:堆積積疊引數登入檔

堆積積疊引數登入檔在中央位置管理堆積積疊例項的引數值,而不是與堆積積疊程式碼一起:

└── env/
    ├── test/
    │   └── cluster/
    │       ├── min = 1
    │       └── max = 1
    ├── staging/
    │   └── cluster/
    │       ├── min = 2
    │       └── max = 3
    └── production/
        └── cluster/
            ├── min = 2
            └── max = 6

堆積積疊程式碼使用鍵檢索相關值:

cluster:
  id: container_cluster-${environment}
  minimum: ${get_value("/env/${environment}/cluster/min")}
  maximum: ${get_value("/env/${environment}/cluster/max")}

這種方法的優點是:

  • 將設定與實作分離
  • 引數可以由不同的工具設定、使用和檢視
  • 可以作為基礎設施和系統設定的真實來源

但缺點包括:

  • 需要設定登入檔,這是系統的額外移動部分
  • 登入檔是堆積積疊的依賴項和潛在故障點
  • 在災難還原場景中可能很痛苦

設定登入檔的實作

設定登入檔可以透過多種方式實作:

  1. 基礎設施自動化工具登入檔:如Chef Infra Server、PuppetDB、Ansible Tower等

  2. 通用設定登入檔產品:如Zookeeper、etcd、Consul、doozerd等

  3. 平台登入檔服務:如AWS SSM Parameter Store等雲平台提供的鍵值儲存服務

  4. DIY設定登入檔:透過在中央位置儲存設定檔案或使用分散式儲存來構建自定義輕量級設定登入檔

處理機密作為引數

系統需要各種機密,如密碼或API金鑰。處理這些機密的方法包括:

  1. 加密機密:使用git-crypt、blackbox、sops等工具在原始碼中加密機密

  2. 無機密授權:許多服務和系統提供無需使用機密即可授權操作的方法,如AWS IAM角色

  3. 執行時注入機密:在執行時注入機密,而不是將其儲存在程式碼中

  4. 一次性機密:動態建立機密並僅在「需要知道」的基礎上使用它們

可重用堆積積疊應該是大多數需要管理大型基礎設施團隊的首選模式。堆積積疊是測試和交付變更的有用單位,它確保每個環境例項一致定義和建構。堆積積疊作為變更單位的完整性增強了快速與頻繁交付變更的能力。

設定應該最小化。如果發現不同堆積積疊例項需要彼此有根本不同,應將它們定義為不同的堆積積疊。堆積積疊專案應定義在例項之間保持一致的堆積積疊形狀。擁有兩個定義表面上相似但形狀不同的堆積積疊專案是完全可以接受的。

選擇適合團隊需求的設定模式至關重要,同時確保機密的安全處理。透過遵循這些最佳實踐,團隊可以建立可靠、一致與安全的多堆積積疊基礎設施環境。

基礎設施交付管道:自動化測試與佈署流程

在現代軟體開發中,基礎架構即程式碼(Infrastructure as Code)的實踐需要一套完善的交付管道,以確保程式碼變更能夠被可靠地測試和佈署。本文將探討如何設計一個漸進式測試的管道,以及如何處理基礎設施變更的自動化流程。

管道的核心理念:自動化與一致性

當開發人員將程式碼變更推播到版本控制系統時,團隊需要一個中央系統來處理這些變更,並將其透過一系列階段進行測試和交付。這個過程應該是自動化的,儘管可能需要人工參與來觸發或批准某些活動。

管道的主要功能是自動化與程式碼封裝、提升和應用相關的流程,以及執行測試。人員可能會審查變更,甚至在環境中進行探索性測試,但他們不應該手動執行命令來佈署和應用變更,也不應該在過程中臨時選擇設定選項或做出其他決策。這些操作應該被定義為程式碼並由系統執行。

自動化流程確保每次、每個階段都能一致地執行,這提高了測試的可靠性,並在基礎設施例項之間建立了一致性。

管道的錯誤處理原則

每個變更都應該從管道的起點推播。如果在管道的「下游」(後期)階段發現錯誤,不要在該階段修復並繼續透過管道的其餘部分。相反,應該在程式碼函式庫中修復程式碼,並從管道的起點推播新的變更。這種做法確保每個變更都經過完整測試。

例如,當一個變更成功透過管道,而第二個變更在管道中間失敗時,修復應該從頭開始重新執行整個管道,而不是從失敗點繼續。

管道階段的特性

管道的每個階段可能執行不同的任務,並可能以不同的方式觸發。一個管道階段的特性包括:

  1. 觸發器:導致階段開始執行的事件。它可能在程式碼推播到程式碼函式庫時自動執行,或在管道中前一個階段成功執行後執行。也可能由人員手動觸發,例如當測試人員或發布管理員決定將程式碼變更應用到特定環境時。

  2. 活動:階段執行時發生的事情。一個階段可能執行多個操作,例如應用程式碼來設定基礎設施堆積積疊、執行測試,然後銷毀堆積積疊。

  3. 批准:階段如何被標記為透過或失敗。系統可以在命令無錯誤執行與自動化測試全部透過時自動將階段標記為透過(通常稱為「綠色」)。或者可能需要人員標記階段為已批准,例如測試人員在進行探索性測試後批准階段。也可以使用手動批准階段來支援治理簽核。

  4. 輸出:階段產生的成品或其他材料。典型的輸出包括基礎設施程式碼包或測試報告。

漸進式測試策略

在漸進式測試策略中,早期階段驗證單個元件,而後期階段整合元件並一起測試它們。例如,一個階段可能執行多個元件的測試,如一套單元測試;或者,不同的元件可能各自有一個單獨的測試階段。

階段中測試的依賴範圍

系統的許多元素依賴於其他服務。例如,應用伺服器堆積積疊可能連線到身份管理服務來處理使用者認證。要漸進式地測試這一點,你可能首先執行一個不使用身份管理服務的階段,可能使用模擬服務來代替它。後續階段將在應用伺服器與身份管理服務的測試例項整合的情況下執行額外的測試,而生產階段將與生產例項整合。

階段所需的平台元素

平台服務是系統的一種特殊依賴型別。你的系統最終可能在基礎設施平台上執行,但你可能夠在離線狀態下有效地執行和測試其中的部分。

例如,定義網路結構的程式碼需要在雲平台上設定這些結構才能進行有意義的測試。但你可能夠在本地虛擬機器甚至容器中測試安裝應用伺服器包的程式碼,而不需要在雲平台上建立虛擬機器。因此,早期測試階段可能夠在不使用完整雲平台的情況下執行某些元件。

只包含增值階段

避免在管道中建立不必要的階段,因為每個階段都會為交付過程增加時間和成本。因此,不要僅為了完整性而為每個元件和整合建立單獨的階段。只有當分階段測試能夠增加足夠的價值以抵消額外開銷時,才這樣做。可能驅使你這樣做的原因包括速度、可靠性、成本和控制。

交付管道軟體與服務

要構建管道,你需要軟體或託管服務。管道系統需要做幾件事:

  • 提供設定管道階段的方式
  • 從不同的操作觸發階段,包括自動化事件和手動觸發
  • 支援你可能需要的任何操作,包括應用基礎設施程式碼和執行測試
  • 處理階段的成品和其他輸出,包括能夠將它們從一個階段傳遞到下一個階段
  • 幫助你追蹤和關聯特定版本和程式碼例項、成品、輸出和基礎設施

管道系統的選項包括:

  1. 構建伺服器:如Jenkins、Team City、Bamboo或GitHub Actions
  2. CD軟體:如GoCD、ConcourseCI和BuildKite
  3. SaaS服務:如CircleCI、TravisCI、AppVeyor、Drone和BoxFuse
  4. 雲平台服務:如AWS CodeBuild和AWS CodePipeline,以及Azure Pipelines
  5. 原始碼函式庫服務:如GitHub Actions和GitLab CI/CD

此外,還有一些專為基礎架構即程式碼設計的產品和服務,如Atlantis、Terraform Cloud和WeaveWorks的GitOps工具。

在生產環境中測試

在將發布和變更應用到生產環境之前進行測試是行業的重點。隨著系統複雜性和規模的增加,你能夠在生產環境之外檢查的風險範圍縮小。這並不是説在發布前測試變更沒有價值,但相信發布前測試可以全面覆寫風險會導致:

  • 在發布前測試上過度投資,遠超過收益遞減點
  • 在生產環境測試上投資不足

無法在生產環境外複製的特性

生產環境有幾個特性是你無法在生產環境外實際複製的:

  1. 資料:生產系統可能有更大的資料集,而與無疑會有意外的資料值和組合。
  2. 使用者:由於數量龐大,使用者在做奇怪的事情方面比測試人員更有創意。
  3. 流量:如果系統有不平凡的流量水平,你無法複製它將定期經歷的活動數量和型別。
  4. 並發性:測試工具可以模擬多個使用者同時使用系統,但無法複製使用者同時做的不尋常組合。

這些特性帶來的兩個挑戰是:它們創造了你無法預測的風險,以及你無法在生產環境之外很好地複製的條件。

在生產環境中執行測試,你可以利用那裡存在的條件——大型自然資料集和不可預測的並發活動。

在生產環境中測試的風險管理

在生產環境中測試會帶來新的風險。有幾件事可以幫助管理這些風險:

  1. 監控:有效的監控提供信心,讓你能夠檢測試造成的問題,以便快速停止它們。
  2. 可觀察性:可觀察性讓你能夠以幫助調查和修復問題的詳細程度瞭解系統內部發生的情況。
  3. 零停機佈署:能夠快速無縫地佈署和回復變更有助於降低錯誤風險。
  4. 漸進式佈署:如果你可以同時執行元件的不同版本,或為不同的使用者集提供不同的設定,你可以在生產條件下測試變更,然後再將其暴露給使用者。
  5. 資料管理:生產測試不應對資料進行不適當的更改或暴露敏感資料。
  6. 混沌工程:透過故意注入已知型別的故障來降低生產環境中的風險,以證明緩解系統正常工作。

監控作為測試

監控可以被視為生產環境中的被動測試。這不是真正的測試,因為你不是採取行動並檢查結果,而是觀察使用者的自然活動並觀察不良結果。

監控應該形成測試策略的一部分,因為它是你為管理系統風險而做的事情的一部分。

測試基礎設施堆積積疊:從單元測試到整合測試

測試基礎設施堆積積疊需要一套全面的策略,從離線測試到線上測試,再到處理依賴關係和測試例項的生命週期。本文將探討如何有效地測試基礎設施堆積積疊,以確保其可靠性和穩定性。

範例基礎設施

以ShopSpinner團隊為例,他們使用可重用的堆積積疊專案為每個客戶建立一致的應用基礎設施例項。這也可以用於在管道中建立基礎設施的測試例項。

這個範例中的基礎設施是一個標準的三層系統,包括:

  1. Web伺服器容器叢集:團隊為每個區域和每個測試環境執行一個Web伺服器容器叢集。該區域或環境中的應用分享此叢集。

  2. 應用伺服器:每個應使用案例項的基礎設施包括一個虛擬機器、一個持久磁碟卷和網路包括地址塊、閘道器、到伺服器網路連線埠的路由以及網路存取規則。

  3. 資料函式庫:ShopSpinner為每個客戶應使用案例項執行一個單獨的資料函式庫例項,使用其提供商的DBaaS。ShopSpinner的基礎設施程式碼還定義了地址塊、路由以及資料函式庫認證和存取規則。

離線測試階段

離線階段在執行階段的服務的代理節點上「本地」執行,而不需要在基礎設施平台上設定基礎設施。嚴格的離線測試完全在本地伺服器或容器例項內執行,不連線到任何外部服務。更寬鬆的離線階段可能連線到現有的服務例項,甚至是雲API,但不使用任何真實的堆積積疊基礎設施。

離線階段應該:

  • 快速執行,如果有不正確的地方能夠提供快速反饋
  • 驗證隔離元件的正確性,以增強對每個元件的信心,並簡化故障除錯
  • 證明元件是乾淨解耦的

你可以在離線階段對堆積積疊程式碼進行的測試包括:

1. 語法檢查

使用大多數堆積積疊工具,你可以執行一個乾執行命令,該命令解析你的程式碼而不將其應用於基礎設施。如果有語法錯誤,命令會以錯誤結束。這種檢查能夠非常快速地告訴你何時在程式碼變更中犯了錯誤,但會錯過許多其他錯誤。

例如,失敗的語法檢查輸出可能如下所示:

$ stack validate
Error: Invalid resource type
on appserver_vm.infra line 1, in resource "virtual_mahcine":
stack does not support resource type "virtual_mahcine".

2. 離線靜態程式碼分析

一些工具可以解析和分析堆積積疊原始碼,查詢比語法更廣泛的問題,但仍然不連線到基礎設施平台。這種分析通常被稱為linting。這類別工具可能會尋找編碼錯誤、混淆或糟糕的編碼風格、遵守程式碼風格政策或潛在的安全問題。

例如,一個虛構的分析工具的錯誤可能如下所示:

$ stacklint
1 issue(s) found:
Notice: Missing 'Name' tag (vms_must_have_standard_tags)
on appserver_vm.infra line 1, in resource "virtual_machine":

3. 使用API進行靜態程式碼分析

根據工具的不同,一些靜態程式碼分析檢查可能連線到雲平台API,以檢查與平台支援的內容是否存在衝突。例如,tflint可以檢查Terraform專案程式碼,以確保程式碼中定義的任何例項型別或AMI實際存在。

4. 使用模擬API進行測試

你可能夠將堆積積疊程式碼應用到本地模擬的基礎設施平台API例項。目前為止,我所知道的唯一個模擬這些API的工具是Localstack。一些工具可以模擬平台的部分功能,如Azurite,它模擬Azure blob和佇列儲存。

線上測試階段

線上階段涉及使用基礎設施平台建立和與堆積積疊例項互動。這種型別的階段較慢,但可以進行比線上測試更有意義的測試。交付管道服務通常在其節點或代理之一上執行堆積積疊工具,但它使用平台API與堆積積疊例項互動。

線上階段可以執行的測試包括:

1. 預覽:檢視將要進行的更改

一些堆積積疊工具可以將堆積積疊程式碼與堆積積疊例項進行比較,列出它將進行的更改,而不實際更改任何內容。Terraform的plan子命令是一個著名的例子。

通常,人們會對生產例項進行更改預覽作為安全措施,以便有人可以審查更改列表,確保不會發生意外情況。你可以編寫自動化測試來檢查預覽命令的輸出,例如根據策略檢查更改,如果程式碼建立了已棄用的資源型別,則失敗。

2. 驗證:對基礎設施資源進行斷言

給定一個堆積積疊例項,你可以線上階段中進行測試,對堆積積疊中的基礎設施進行斷言。例如:

given virtual_machine(name: "appserver-testcustomerA-staging") {
  it { exists }
  it { is_running }
  it { passes_healthcheck }
  it { has_attached storage_volume(name: "app-storage-testcustomerA-staging") }
}

簡單的斷言通常價值有限,因為它們只是重述了它們正在測試的基礎設施程式碼。一些基本斷言(如exists)有助於理智檢查程式碼是否成功應用。

3. 結果:證明基礎設施正常工作

功能測試是應用軟體測試的重要部分。與基礎設施的類別比是證明你可以按預期使用基礎設施。例如:

given stack_instance(stack: "shopspinner_networking",
                    instance: "online_test") {
  can_connect(ip_address: stack_instance.appserver_ip_address,
              port:443)
  http_request(ip_address: stack_instance.appserver_ip_address,
              port:443,
              url: '/').response.code is('200')
}

使用測試夾具處理依賴關係

許多堆積積疊專案依賴於在堆積積疊外部建立的資源,例如在不同堆積積疊專案中定義的分享網路。測試夾具是一種基礎設施資源,專門用於幫助你設定和測試堆積積疊例項本身,而不需要其他堆積積疊的例項。

使用測試夾具使管理測試變得更容易,保持堆積積疊鬆散耦合,並擁有快速反饋迴圈。測試夾具不是你正在測試的堆積積疊的一部分,而是你建立的額外基礎設施,用於支援你的測試。

給定的依賴關係可以是上游的(意味著你正在測試的堆積積疊使用另一個堆積積疊提供的資源),也可以是下游的(在這種情況下,其他堆積積疊使用你正在測試的堆積積疊中的資源)。

上游依賴的測試替身

當你需要測試依賴於另一個堆積積疊的堆積積疊時,你可以建立一個測試替身。對於堆積積疊,這通常意味著建立一些額外的基礎設施。在分享網路堆積積疊和應用堆積積疊的例子中,應用堆積積疊需要在網路堆積積疊定義的網路地址塊中建立其伺服器。你的測試設定可能夠建立一個地址塊作為測試夾具,以單獨測試應用堆積積疊。

下游依賴的測試夾具

你也可以使用測試夾具處理相反的情況,測試為其他堆積積疊提供資源的堆積積疊。例如,堆積積疊例項定義了ShopSpinner的網路結構,包括Web伺服器容器叢集和應用伺服器的段和路由。網路堆積積疊不設定容器叢集或應用伺服器,因此為了測試網路,設定在這些段中的每一個中設定了一個測試夾具。

測試堆積積疊例項的生命週期模式

在虛擬化和雲之前,每個人都維護靜態的、長壽命的測試環境。雖然許多團隊仍然擁有這些環境,但按需建立和銷毀環境有很多優勢。以下模式描述了保持久堆積積疊例項、為每次測試執行建立臨時例項以及結合兩種方法的權衡。

模式:持久測試堆積積疊

測試階段可以使用始終執行的持久測試堆積積疊例項。該階段將每個程式碼變更作為對現有堆積積疊例項的更新應用,執行測試,並將生成的修改後的堆積積疊留在原地供下一次執行使用。

動機:通常,對現有堆積積疊例項應用變更比建立新例項要快得多。因此,持久測試堆積積疊可以提供更快的反饋。

適用性:當你可以可靠地將堆積積疊程式碼應用於例項時,持久測試堆積積疊很有用。如果你發現自己花時間修復損壞的例項以使管道再次執行,你應該考慮本章中的其他模式。

後果:堆積積疊例項在變更失敗並使其處於任何新嘗試應用堆積積疊程式碼也會失敗的狀態時,經常會變得「卡住」。因此,你的團隊花太多時間手動修復損壞的測試例項。

模式:臨時測試堆積積疊

使用臨時測試堆積積疊模式,測試階段每次執行時都會建立和銷毀堆積積疊的新例項。

動機:臨時測試堆積積疊為每次測試執行提供一個乾淨的環境。不存在來自先前執行的資料、夾具或其他「雜物」的風險。

適用性:你可能想為快速設定的堆積積疊使用臨時例項。「快速」相對於你和你的團隊需要的反饋迴圈。對於更頻繁的變更,如快速開發階段中的應用程式碼提交,構建新環境的時間可能比人們能夠容忍的要長。但對於不太頻繁的變更,如OS補丁更新,可能可以接受使用完整重建進行測試。

後果:堆積積疊通常需要很長時間才能從頭開始設定。因此,使用臨時堆積積疊例項的階段使反饋迴圈和交付週期變慢。

反模式:雙重持久和臨時堆積積疊階段

使用持久和臨時堆積積疊階段,管道將每個堆積積疊變更傳送到兩個不同的階段,一個使用臨時堆積積疊例項,一個使用持久堆積積疊例項。

動機:團隊通常實施這一點是為瞭解決它結合的兩種模式的缺點。如果一切順利,「快速而髒」的階段(使用持久例項的那個)提供快速反饋。如果該階段因環境變得卡住而失敗,你最終會從「慢而乾淨」的階段(使用臨時例項的那個)得到反饋。

後果:實際上,使用這兩種型別的堆積積疊生命週期結合了兩者的缺點。如果更新現有堆積積疊不可靠,那麼當出錯時,你的團隊仍將花時間手動修復該階段。而與你可能會等到較慢的階段透過後才確信變更是好的。

模式:定期堆積積疊重建

定期堆積積疊重建使用持久測試堆積積疊例項進行堆積積疊測試階段,然後有一個帶外執行的過程,按計劃銷毀和重建堆積積疊例項,例如每晚。

動機:人們經常使用定期重建來降低成本。他們在工作日結束時銷毀堆積積疊,並在下一天開始時設定一個新的。

適用性:重建堆積積疊例項以解決資源使用問題通常會掩蓋潛在問題或設計問題。在這種情況下,這種模式充其量是一種臨時駭客手段,最壞的情況是允許問題積累直到造成災難。

後果:如果你使用這種模式來釋放閒置資源,你需要考慮如何確保它們不需要。例如,在辦公時間之外或在其他時區工作的人可能會因為沒有測試環境而被阻止。

模式:連續堆積積疊重置

使用連續堆積積疊重置模式,每次堆積積疊測試階段完成時,一個帶外作業都會銷毀並重建堆積積疊例項。

動機:每次銷毀和重建堆積積疊例項都為每次測試執行提供一個乾淨的狀態。它可能會自動移除損壞的例項,除非它太損壞,堆積積疊工具無法銷毀它。而與它從反饋迴圈中移除了建立和銷毀堆積積疊例項所需的時間。

適用性:如果堆積積疊專案不傾向於損壞並需要手動干預來修復,那麼在後台銷毀堆積積疊例項可能效果很好。

後果:由於堆積積疊在管道的交付流程之外被銷毀和設定,問題可能不可見。管道可以是綠色的,但測試例項可能在幕後損壞。當下一個變更到達測試階段時,可能需要時間才能意識到它失敗是因為後台作業而不是因為變更本身。

測試協調

測試協調可能涉及:

  • 建立測試夾具
  • 載入測試資料
  • 管理測試堆積積疊例項的生命週期
  • 向測試工具提供引數
  • 執行測試工具
  • 整合測試結果
  • 清理測試例項、夾具和資料

協調測試時要考慮的兩個指導原則是支援本地測試和避免與管道工具緊密耦合。

支援本地測試

處理基礎設施堆積積疊程式碼的人應該能夠在將程式碼推播到分享管道和環境之前自己執行測試。這允許你在推播變更之前編碼和執行線上測試。

避免與管道工具緊密耦合

許多CI和管道協調工具都有用於測試協調的功能或外掛,甚至可以為你設定和執行測試。雖然這些功能可能看起來很方便,但它們使得在管道之外一致地設定和執行測試變得困難。混合測試和管道設定也可能使更改變得痛苦。

相反,你應該在單獨的指令碼或工具中實作測試協調。測試階段應該呼叫此工具,傳遞最少的設定引數。這種方法使管道協調和測試協調的關注點鬆散耦合。

測試基礎設施堆積積疊需要一套全面的策略,從離線測試到線上測試,再到處理依賴關係和測試例項的生命週期。透過適當的測試策略,團隊可以確保基礎設施程式碼的可靠性和穩定性,同時保持快速的反饋迴圈和高效的交付流程。