可靠的自動化IT系統需要人員持續建構、修復、調整和改進。基礎設施系統通常涉及以下幾種角色,這些角色並不總是一對一對映到個人—有些人可能扮演多個角色,有些角色可能由多人分享:

核心角色定義

  1. 使用者:直接使用基礎設施的人員。在許多組織中,這些是應用程式團隊,可能是開發應用程式的團隊,或設定和管理第三方應用程式的團隊。

  2. 治理工作者:制定各種領域政策的人員,包括安全、法規遵循、架構、效能、成本控制和正確性。

  3. 設計師:設計基礎設施的人員。在某些組織中,這些人是架構師,可能分為不同領域,如網路或儲存。

  4. 工具製作者:提供服務、工具和元件給其他團隊用於建構或執行環境的人員。例如監控團隊或建立可重用基礎設施程式碼函式庫的開發人員。

  5. 建構者:建構和變更基礎設施的人員。他們可以透過控制枱或其他介面手動操作,執行指令碼,或執行應用基礎設施程式碼的工具。

  6. 測試者:驗證基礎設施的人員。這個角色不限於QA(品質分析師),還包括測試或審查系統安全性或效能等治理領域的人員。

  7. 支援人員:確保系統持續正確執行並在出現問題時修復的人員。

傳統結構中,變更系統工作流程的每個部分都有專門的團隊。許多角色可能跨不同的基礎設施領域(如網路、儲存或伺服器)分工。它們也可能跨安全、合規、架構和效能等治理領域分工。許多大型組織建立了複雜的微專業組織結構。

然而,一個人或團隊也常跨這些角色工作。例如,資訊安全團隊可能制定標準、提供掃描工具並進行安全稽核。

誰來編寫基礎設施程式碼?

組織對於誰來編寫和編輯基礎設施程式碼有幾種不同的方式:

不同的程式碼編寫模式

  1. 建構者編寫程式碼: 一些組織試圖保持傳統流程和團隊結構。因此,建構(可能還支援)基礎設施的團隊使用基礎設施即程式碼工具來最佳化其工作。使用者請求環境,建構團隊使用其工具和指令碼為他們建構。然而,僅最佳化建構團隊的流程往往不會改善端對端流程的速度或品質。

  2. 使用者編寫程式碼: 許多組織使應用程式團隊能夠定義其應用程式使用的基礎設施。這使用者需求與解決方案保持一致。然而,這要麼需要每個團隊都包含具有強大基礎設施專業知識的人員,要麼需要簡化定義基礎設施的工具。工具的挑戰在於確保它滿足應用程式團隊的需求,而不是限制他們。

  3. 工具製作者編寫程式碼: 專業團隊可以建立平台、函式庫和工具,使用者能夠定義他們需要的基礎設施。在這些情況下,使用者傾向於編寫設定而非程式碼。工具製作者和建構者編寫程式碼的區別在於自助服務。建構團隊根據使用者請求編寫和使用程式碼來建立或變更環境。工具製作者團隊編寫程式碼,使用者可以自行建立或變更環境。

  4. 治理和測試者編寫程式碼: 制定政策和標準的人員,以及需要確保變更的人員,可以建立工具幫助其他人驗證自己的程式碼。這些人可能成為工具製作者或與工具製作者密切合作。

使用價值流對映改進工作流程

價值流對映是分解前置時間的有用方法,可以瞭解時間的去向。透過測量各種活動(包括等待)所花費的時間,可以將改進重點放在最有影響的領域。

我們經常最佳化流程中看似最明顯效率低下的部分,但這些部分對總前置時間影響很小。例如,我曾見過團隊實施自動化,將佈建伺服器的時間從8小時縮短到10分鐘。這是佈建伺服器時間的98%減少。然而,如果使用者通常等待10天才能獲得新伺服器,總減少只有不太令人興奮的10%。如果伺服器請求工單平均等待8天,則應該將精力集中在那裡。

價值流對映使完成操作的時間變得可見,以便找到最佳改進機會。在進行改進時,繼續測量端對端前置時間和失敗率等指標。這有助於避免對流程一部分的最佳化使整個流程變得更糟。

將程式碼應用於基礎設施:從本地工作站應用程式碼的挑戰

從命令列應用基礎設施程式碼對於沒有其他人使用的基礎設施測試例項可能很有用。但從本地工作環境執行工具會為分享的基礎設施例項帶來問題,無論是生產環境還是交付環境。

人們可能在應用程式碼前對本地版本進行更改。如果他們在將更改推播到分享儲存函式庫之前應用程式碼,那麼其他人就無法存取該版本的程式碼。如果需要除錯基礎設施,這可能會造成問題。

如果應用本地版本程式碼的人沒有立即推播更改,其他人可能會提取和編輯較舊版本的程式碼。當他們應用該程式碼時,會還原第一個人的更改。這種情況很快變得混亂與難以解開。

值得注意的是,鎖定解決方案(如Terraform的狀態鎖定)不會阻止這種情況。鎖定可以防止兩個人同時將程式碼應用到同一例項。但截至目前,鎖定解決方案不會阻止人們應用不同版本的程式碼,只要他們各自等待輪到自己。

因此,對於任何基礎設施例項,程式碼應始終從同一位置應用。你可以指定一個人負責每個例項。但這有許多陷阱,包括對一個人及其工作站的風險依賴。更好的解決方案是使用中央系統來處理分享基礎設施例項。

從中央化服務應用程式碼的優勢

可以使用中央化服務將基礎設施程式碼應用於例項,無論是自己託管的應用程式還是第三方服務。該服務從原始碼儲存函式庫或工件儲存函式庫提取程式碼並將其應用於基礎設施,為管理應用哪個版本的程式碼提供明確、受控的流程。

如果兩個人提取和編輯程式碼,他們必須在將程式碼與工具使用的分支整合時解決程式碼中的任何差異。當出現問題時,很容易看到應用了哪個版本的程式碼並進行更正。

中央服務還確保基礎設施工具一致執行,而不是假設人不會犯錯或"改進"(偏離)工作流程。它每次都使用相同版本的工具、指令碼和支援實用程式。

使用中央服務與跨環境交付基礎設施程式碼的管道模型很好地結合。無論使用什麼工具或服務來協調變更透過管道,都承擔了執行基礎設施工具的責任,將最新版本的程式碼應用到每個環境。

讓中央服務執行基礎設施程式碼的另一個好處是,它迫使你和你的團隊自動化整個流程。如果從工作站執行工具,很容易留下一些鬆散的環節,需要在執行工具前後手動完成的步驟。中央服務除了確保任務完全自動化外別無選擇。

可執行基礎設施工具的工具和服務

有幾個選項可作為中央化服務來應用基礎設施程式碼:

  • 如果使用像Jenkins這樣的構建伺服器或像GoCD或ConcourseCI這樣的CD工具,可以實作執行基礎設施工具的作業或階段。這些工具支援管理源儲存函式庫中不同版本的程式碼,並可以在階段之間推程式式碼。這些多用途工具還使跨應用程式、基礎設施和系統其他部分的工作流程整合變得容易。

  • 幾家供應商提供專門用於執行基礎設施工具的產品或服務。例如Terraform Cloud、Atlantis和Pulumi for Teams。WeaveWorks提供Weave Cloud,將基礎設施程式碼應用於Kubernetes叢集。

個人基礎設施例項的管理

在大多數工作流程中,你提取程式碼,編輯它,然後將其推播到分享程式碼儲存函式庫。然後,管道交付流程將其應用於相關環境。

理想情況下,你可以在將程式碼推播到分享儲存函式庫之前測試程式碼更改。這提供了一種方法來確保更改達到預期效果,比等待管道將程式碼執行到線上測試階段更快。它還有助於避免在更改失敗時破壞構建,打擾所有在程式碼函式庫上工作的人。

團隊可以做幾件事來使推播前測試程式碼更改變得更容易:

  1. 確保個人例項可用: 確保每個在基礎設施程式碼上工作的人都可以建立自己的基礎設施例項。人們在沒有雲平台的情況下本地測試的能力是有限的。你可能會想要執行分享的"開發"基礎設施例項。但如前所述,讓多人將本地編輯的程式碼應用到分享例項會變得混亂。因此,建立一種方式讓人們可以啟動自己的基礎設施例項,並在不積極使用時銷毀它們。

  2. 保持基礎設施元件小型化: 這當然是本章中三個核心實踐之一。你應該能夠獨立啟動系統任何元件的例項,可能使用測試夾具來處理依賴關係。如果人們需要啟動整個系統,那麼個人例項很難工作,除非系統非常小。

  3. 使用一致的工具和指令碼: 人們應該使用與分享例項(例如在管道中)相同的工具和指令碼來應用和測試他們的基礎設施例項。建立可以跨這些不同位置使用的工具和指令碼包很有幫助。

中央管理的個人例項

人們從工作站將程式碼應用到個人例項比應用到分享例項更安全。但對於個人例項使用中央化服務可能有優勢。

我曾見過一個團隊在某人休假時留下執行的個人例項而苦於拆除它。他們使用未推播到儲存函式庫的基礎設施程式碼的本地版本建立了該例項,使其難以銷毀。

因此,一些團隊建立了一種實踐,每個人將更改推播到個人分支,中央服務將其應用到他們可以測試的個人基礎設施例項。在這種安排中,個人分支模擬本地程式碼,因此人們在將其合併到分享分支或主幹之前不認為更改已提交。但程式碼可供其他人在他們缺席時檢視和使用。

原始碼分支在工作流程中的應用

分支是分享源儲存函式庫的強大功能,使人們更容易對程式碼函式庫的不同副本(分支)進行更改,然後在準備好時整合工作。有許多策略和模式可以將分支作為團隊工作流程的一部分。

在基礎設施即程式碼的背景下,值得強調分支策略的幾個區別。一個是生產路徑模式和整合模式之間的區別。另一個是整合頻率的重要性。

團隊使用生產路徑分支模式來管理將哪些版本的程式碼應用於環境。典型的生產路徑模式包括發布分支和環境分支的整合模式描述了在程式碼函式庫上工作的人管理何時以及如何整合工作的方式。大多數團隊使用主線整合模式,無論是使用功能分支還是持續整合。

具體的模式或策略不如使用方式重要。團隊有效使用分支的最重要因素是整合頻率,即每個人將所有程式碼合併到中央儲存函式庫的同一(主)分支的頻率。DORA Accelerate研究發現,團隊內所有程式碼的更頻繁整合與更高的商業績效相關。他們的結果表明,團隊中的每個人應該至少每天將所有程式碼整合在一起(例如,到主分支或主幹)。

合併不等於整合

人們有時會將構建伺服器自動在分支上執行測試與持續整合混淆。持續整合的實踐,以及與更高團隊績效的相關性,是根據完全整合每個人在程式碼函式庫中工作的所有更改。

雖然使用功能分支模式的人可能經常將當前主分支合併到自己的分支,但他們通常不會將自己的工作整合回主分支,直到完成功能。如果其他人以相同方式在自己的功能分支上工作,那麼程式碼在每個人完成功能並將更改合併到主分支之前不會完全整合。

整合涉及雙向合併—個人將自己的更改合併到主分支,以及將主分支合併回自己的分支或程式碼的本地副本。因此,持續整合意味著每個人在工作時都這樣做,至少每天一次。

防止設定漂移的策略:防止設定漂移的有效策略

在第二章中,我們討論了設定漂移的危險(設定漂移是指類別似的基礎設施元素隨著時間變得不一致)。設定漂移通常發生在團隊使用基礎設施編碼工具自動化部分舊有工作方式,而非完全調整其工作方式時。以下是幾種可以在工作流程中避免設定漂移的方法。

最小化自動化延遲

自動化延遲是指執行自動化流程(如應用基礎設施程式碼)的間隔時間。上次執行流程的時間越長,失敗的可能性就越大。即使沒有人刻意進行變更,系統也會隨著時間而改變。

即使程式碼沒有變更,長時間隔後重新應用它仍可能因各種原因而失敗:

  • 有人可能修改了系統的其他部分(如依賴項),這種變更只有在重新應用程式碼時才會導致問題
  • 用於應用程式碼的工具或服務的升級或設定變更可能與您的程式碼不相容
  • 應用未變更的程式碼可能仍會引入傳遞性依賴項的更新,例如作業系統套件
  • 有人可能進行了手動修復或改進,但忘記將其合併回程式碼中,重新應用程式碼會還原這些修復

自動化延遲的推論是:您越頻繁地應用基礎設施程式碼,失敗的可能性就越小。當失敗確實發生時,您可以更快地發現原因,因為自上次成功執行以來變更較少。

避免臨時應用

一些團隊從「鐵器時代」工作方式中延續下來的習慣是隻在需要特定變更時才應用程式碼。他們可能只使用基礎設施程式碼來設定新的基礎設施,而不用於對現有系統進行變更。或者,他們可能編寫並應用基礎設施程式碼來對系統特定部分進行臨時變更。例如,他們為其中一個應用程式伺服器編寫一次性設定變更。即使團隊使用程式碼進行變更,並將程式碼應用於所有例項,有時他們可能只在對程式碼進行變更時才應用程式碼。

這些習慣可能導致設定漂移或自動化延遲。

持續應用程式碼

消除設定漂移的核心策略是持續將基礎設施程式碼應用於例項,即使程式碼沒有變更。許多伺服器設定工具,包括Chef和Puppet,都設計為按計劃重新應用設定,通常是每小時一次。

GitOps方法(將在下文討論)涉及從原始碼分支援續將程式碼應用到每個環境。您應該能夠使用中央服務來應用程式碼(如「從中央服務應用程式碼」中所述),以持續重新應用程式碼到每個例項。

不可變基礎設施

不可變基礎設施以不同的方式解決設定漂移問題。與頻繁地將設定程式碼應用於基礎設施例項不同,您只在建立例項時應用一次。當程式碼變更時,您建立一個新例項並用它替換舊例項。

透過建立新例項進行變更需要複雜的技術來避免停機時間(「零停機時間變更」),並且可能不適用於所有使用案例。自動化延遲仍然是一個潛在問題,因此使用不可變基礎設施的團隊傾向於頻繁重建例項,就像鳳凰伺服器一樣。

鳳凰伺服器(Phoenix Server)是一種經常被重建的伺服器,目的是確保設定過程的可重複性。這種概念也可以應用於其他基礎設施結構,包括基礎設施堆積積疊。這種方法源於「鳳凰涅槃重生」的概念,透過定期完全重建伺服器來確保它始終與程式碼定義保持一致。

GitOps工作流程

GitOps是基礎設施即程式碼的一種變體,涉及從原始碼分支援續同步程式碼到環境。GitOps強調將系統定義為程式碼(見「核心實踐:將一切定義為程式碼」)。

GitOps並不規定測試和交付基礎設施程式碼的方法,但它與使用管道交付程式碼(「基礎設施交付管道」)相容。然而,GitOps不鼓勵使用交付成品(「將基礎設施程式碼封裝為成品」),而是透過將程式碼合併到原始碼分支來促程式式碼變更(見「從原始碼函式庫交付程式碼」)。

GitOps的另一個關鍵元素是持續將程式碼同步到系統(「持續應用程式碼」)。GitOps不是在程式碼變更時透過構建伺服器作業或管道階段應用程式碼(「避免臨時應用」),而是使用一個服務持續比較程式碼與系統,減少設定漂移。

一些團隊將其流程描述為GitOps,但只實施環境分支實踐,而不持續將程式碼同步到環境。這很容易導致臨時變更流程,以及為每個環境複製、貼上和編輯程式碼變更的壞習慣,符合複製貼上反模式(見「反模式:複製貼上環境」)。

根據管道工作流程的治理

治理對大多陣列織來説都是一個關注點,尤其是較大的組織和那些在金融和醫療保健等受監管行業工作的組織。有些人將治理視為一個負面詞彙,認為它意味著為有用的工作增加不必要的摩擦。但治理只是確保事情按照組織的政策負責任地完成。

治理在基礎設施即程式碼中並不是一個限制性的概念,而是一種確保系統按照組織標準和政策執行的方式。良好的治理可以透過自動化流程和檢查來實作,這些流程和檢查被整合到交付管道中,使合規成為開發過程的自然部分,而不是事後的審核活動。

基礎設施即程式碼的工作流程最佳實踐

在實施基礎設施即程式碼時,採用正確的工作流程對於維持系統的一致性和可靠性至關重要。以下是一些關鍵的最佳實踐,可以幫助團隊有效地管理基礎設施程式碼。

自動化頻率與設定一致性

頻繁執行自動化流程是保持系統一致性的關鍵。玄貓在多個專案中觀察到,執行自動化的頻率與設定一致性之間存在直接關聯。當團隊每天或每次程式碼變更時應用基礎設施程式碼,系統的可預測性和穩定性顯著提高。

  graph TD
    A[高頻率應用程式碼] --> B[減少自動化延遲]
    B --> C[提高系統一致性]
    C --> D[減少故障排除時間]
    D --> E[增強團隊信心]
    E --> A

從源頭管理變更

所有基礎設施變更都應該從版本控制系統開始。這不僅提供了變更歷史,還確保了變更的可審核性和可追溯性。當團隊需要了解為什麼某個設定以特定方式實施時,版本控制系統提供了寶貴的上下文。

// 基礎設施變更工作流程範例
function infrastructureChangeWorkflow() {
  // 1. 在版本控制系統中建立分支
  const branch = createFeatureBranch('update-network-config');
  
  // 2. 在分支中進行變更
  updateNetworkConfiguration(branch, {
    securityGroups: updatedSecurityGroups,
    routingRules: newRoutingRules
  });
  
  // 3. 提交變更並建立合併請求
  commitChanges(branch, 'Update network security groups and routing rules');
  createMergeRequest(branch, 'main');
  
  // 4. 自動化測試和審核
  runAutomatedTests(branch);
  requestPeerReview(branch);
  
  // 5. 合併並應用變更
  if (isApproved(branch) && testsPass(branch)) {
    mergeBranch(branch, 'main');
    // 自動觸釋出署流程
    triggerDeployment('main');
  }
}

這段程式碼展示了一個理想的基礎設施變更工作流程。它從建立一個功能分支開始,在該分支中進行網路設定變更,然後提交這些變更並建立合併請求。接著執行自動化測試並請求同行審核。如果變更獲得批准與測試透過,則將分支合併到主分支,並自動觸釋出署流程。這種方法確保所有變更都經過適當的審核和測試,並且可以追溯到特定的請求和目的。

環境一致性策略

維持不同環境(開發、測試、生產)之間的一致性是避免「在我的機器上可以執行」問題的關鍵。使用相同的程式碼和流程來管理所有環境,只是設定引數不同,可以大減少環境差異導致的問題。

# 環境設定範例 - 使用相同結構但不同引數
environments:
  development:
    instance_type: t3.medium
    auto_scaling:
      min_instances: 1
      max_instances: 2
    monitoring:
      log_level: DEBUG
      alerts_enabled: false
      
  staging:
    instance_type: t3.large
    auto_scaling:
      min_instances: 2
      max_instances: 4
    monitoring:
      log_level: INFO
      alerts_enabled: true
      
  production:
    instance_type: m5.xlarge
    auto_scaling:
      min_instances: 3
      max_instances: 10
    monitoring:
      log_level: WARN
      alerts_enabled: true

這個YAML設定展示瞭如何為不同環境維護一致的結構但使用不同的引數。所有環境都有相同的設定類別(例項型別、自動擴充套件、監控),但引數值根據環境需求而調整。開發環境使用較小的例項和最小設定,而生產環境使用更強大的例項和更嚴格的監控設定。這種方法確保了環境之間的結構一致性,同時允許根據每個環境的特定需求進行必要的調整。

實施不可變基礎設施的策略

不可變基礎設施是一種強大的方法,可以消除設定漂移並提高系統可靠性。以下是實施不可變基礎設施的關鍵策略:

  1. 構建標準化映像:使用工具如Packer建立包含所有必要軟體和設定的標準化映像。

  2. 自動化佈署流程:使用基礎設施即程式碼工具(如Terraform、AWS CloudFormation)自動化從映像到執行例項的佈署。

  3. 實施藍綠佈署:使用藍綠佈署策略來實作零停機時間更新,透過建立新環境並在驗證後切換流量。

# Terraform藍綠佈署範例
resource "aws_autoscaling_group" "blue" {
  name                 = "blue-asg"
  launch_configuration = aws_launch_configuration.app_server.id
  min_size             = var.environment == "production" ? 3 : 1
  max_size             = var.environment == "production" ? 10 : 3
  vpc_zone_identifier  = aws_subnet.private.*.id
  
  tag {
    key                 = "Environment"
    value               = var.environment
    propagate_at_launch = true
  }
  
  tag {
    key                 = "Version"
    value               = "blue"
    propagate_at_launch = true
  }
}

resource "aws_autoscaling_group" "green" {
  name                 = "green-asg"
  launch_configuration = aws_launch_configuration.app_server_new.id
  min_size             = 0
  max_size             = 0  # 初始設定為0,佈署時會更新
  vpc_zone_identifier  = aws_subnet.private.*.id
  
  tag {
    key                 = "Environment"
    value               = var.environment
    propagate_at_launch = true
  }
  
  tag {
    key                 = "Version"
    value               = "green"
    propagate_at_launch = true
  }
}

resource "aws_lb_target_group" "blue" {
  name     = "blue-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id
}

resource "aws_lb_target_group" "green" {
  name     = "green-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id
}

resource "aws_lb_listener" "front_end" {
  load_balancer_arn = aws_lb.main.arn
  port              = "80"
  protocol          = "HTTP"
  
  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.blue.arn  # 初始指向藍色環境
  }
}

這段Terraform程式碼展示了藍綠佈署的基本結構。它定義了兩個自動擴充套件組(ASG)—「藍」和「綠」,以及相應的目標組和負載平衡器監聽器。初始狀態下,綠色ASG的大小設定為0(未啟用),而負載平衡器指向藍色目標組。在佈署新版本時,會先擴充套件綠色ASG,等它準備好後,再將負載平衡器切換到綠色目標組。這種方法允許在不中斷服務的情況下完全替換基礎設施,並且如果新版本有問題,可以快速回復到藍色環境。

GitOps實踐的實際應用

GitOps將Git作為單一事實來源,不僅用於應用程式碼,還用於基礎設施和設定。以下是實施GitOps的關鍵元素:

  1. 宣告式設定:所有系統設定都以宣告式方式儲存在Git中。

  2. 自動同步:使用工具(如Flux或ArgoCD)自動將Git中的設定同步到執行環境。

  3. 偏差檢測:持續監控系統狀態與Git中定義的期望狀態之間的差異。

# Flux GitOps設定範例
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
  name: infrastructure-repo
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/organization/infrastructure-repo
  ref:
    branch: main

---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
kind: Kustomization
metadata:
  name: infrastructure
  namespace: flux-system
spec:
  interval: 10m
  path: "./environments/production"
  prune: true
  sourceRef:
    kind: GitRepository
    name: infrastructure-repo
  validation: client
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: api-gateway
      namespace: production
    - apiVersion: apps/v1
      kind: 有狀態集合
      name: database
      namespace: production

這個YAML設定展示了使用Flux實作GitOps的基本設定。它定義了一個Git倉函式庫作為設定源,並設定了每分鐘檢查一次更新。Kustomization資源指定了要應用的特定路徑(生產環境設定),設定為每10分鐘同步一次,並啟用了prune選項(移除不再在Git中定義的資源)。此外,它還定義了健康檢查,以確保關鍵元件(API閘道器和資料函式庫)在同步後正常執行。這種設定確保了生產環境始終與Git倉函式庫中定義的狀態保持同步,任何偏差都會被自動糾正。

治理與合規的自動化

將治理和合規要求整合到基礎設施程式碼工作流程中,可以確保系統始終符合組織政策,同時不會減慢開發速度。

# 自動化合規檢查範例
def compliance_check(infrastructure_code):
    compliance_issues = []
    
    # 檢查安全組規則
    security_groups = extract_security_groups(infrastructure_code)
    for sg in security_groups:
        # 檢查是否有開放的SSH存取
        if has_open_ssh_access(sg):
            compliance_issues.append({
                "severity": "HIGH",
                "message": f"Security group {sg.id} allows unrestricted SSH access",
                "remediation": "Restrict SSH access to specific IP ranges"
            })
        
        # 檢查是否有過於寬鬆的入站規則
        if has_overly_permissive_rules(sg):
            compliance_issues.append({
                "severity": "MEDIUM",
                "message": f"Security group {sg.id} has overly permissive inbound rules",
                "remediation": "Restrict inbound traffic to necessary services only"
            })
    
    # 檢查加密設定
    storage_resources = extract_storage_resources(infrastructure_code)
    for resource in storage_resources:
        if not is_encryption_enabled(resource):
            compliance_issues.append({
                "severity": "HIGH",
                "message": f"Storage resource {resource.id} does not have encryption enabled",
                "remediation": "Enable encryption for all storage resources"
            })
    
    # 檢查標籤合規性
    all_resources = extract_all_resources(infrastructure_code)
    for resource in all_resources:
        missing_tags = check_required_tags(resource)
        if missing_tags:
            compliance_issues.append({
                "severity": "LOW",
                "message": f"Resource {resource.id} is missing required tags: {', '.join(missing_tags)}",
                "remediation": "Add all required tags to the resource"
            })
    
    return compliance_issues

這段Python程式碼展示了一個自動化合規檢查函式,它分析基礎設施程式碼並識別潛在的合規問題。它檢查幾個關鍵領域:安全組規則(尋找開放的SSH存取和過於寬鬆的入站規則)、儲存資源的加密設定,以及資源標籤的合規性。對於每個發現的問題,它提供嚴重性評級、描述性訊息和修復建議。這種自動化檢查可以整合到CI/CD管道中,在佈署前識別和解決合規問題,確保所有基礎設施變更都符合組織的安全和治理標準。

團隊協作與知識分享

基礎設施即程式碼不僅是技術實踐,也是團隊協作的方式。建立有效的協作流程可以提高團隊效率並減少錯誤。

  graph LR
    A[程式碼審核] --> B[知識分享]
    B --> C[標準化實踐]
    C --> D[減少個人依賴]
    D --> E[提高團隊彈性]
    E --> A
    
    F[檔案即程式碼] --> B
    G[結對程式設計] --> B
    H[技術分享會] --> B

有效的團隊協作策略包括:

  1. 程式碼審核:所有基礎設施變更都應經過同行審核,這不僅可以捕捉錯誤,還可以分享知識。

  2. 檔案即程式碼:將檔案與程式碼一起維護,確保它們始終同步。

  3. 標準化模式:開發和使用標準化的基礎設施模式,減少重複工作並提高一致性。

  4. 知識輪換:確保多人瞭解每個系統,避免知識孤島。

實施持續同步的最佳實踐

持續同步是防止設定漂移的關鍵策略。以下是一些實施持續同步的最佳實踐:

定期重新應用設定

即使沒有變更,也應定期重新應用基礎設施程式碼,以確保系統狀態與程式碼定義保持一致。這可以透過排程作業或持續同步工具實作。

#!/bin/bash
# 定期重新應用Terraform設定的指令碼

# 設定環境變數
export TF_LOG=INFO
export TF_LOG_PATH=/var/log/terraform/apply-$(date +%Y%m%d-%H%M%S).log

# 確保日誌目錄存在
mkdir -p /var/log/terraform

# 切換到Terraform設定目錄
cd /opt/terraform/environments/production

# 初始化Terraform(如果需要)
terraform init -input=false

# 應用設定
echo "Starting scheduled Terraform apply at $(date)"
terraform apply -auto-approve -input=false

# 檢查結果
if [ $? -eq 0 ]; then
  echo "Terraform apply completed successfully at $(date)"
  # 傳送成功通知
  /usr/local/bin/notify-team "Scheduled Terraform apply completed successfully"
else
  echo "Terraform apply failed at $(date)"
  # 傳送失敗警示
  /usr/local/bin/alert-team "Scheduled Terraform apply failed, manual intervention required"
fi

這個Bash指令碼展示瞭如何設定期重新應用Terraform設定的排程作業。它設定了適當的環境變數和日誌記錄,初始化Terraform(如果需要),然後使用自動批准選項應用設定。指令碼還包括錯誤處理和通知機制,以便在應用成功或失敗時通知團隊。這種定期重新應用可以捕捉並糾正任何手動變更或外部漂移,確保系統始終與程式碼定義保持一致。

偏差檢測與報告

實施自動化機制來檢測系統狀態與程式碼定義之間的差異,並生成報告以便團隊可以採取行動。

# 基礎設施偏差檢測指令碼
import json
import subprocess
import datetime
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

def detect_terraform_drift():
    # 執行Terraform計劃以檢測偏差
    result = subprocess.run(
        ["terraform", "plan", "-detailed-exitcode", "-out=drift.plan"],
        capture_output=True,
        text=True
    )
    
    # Terraform結束碼:
    # 0 = 無變更
    # 1 = 錯誤
    # 2 = 有變更(偏差)
    
    if result.returncode == 0:
        return {"has_drift": False, "details": "No drift detected"}
    elif result.returncode == 2:
        # 提取計劃詳情
        show_result = subprocess.run(
            ["terraform", "show", "-json", "drift.plan"],
            capture_output=True,
            text=True
        )
        plan_json = json.loads(show_result.stdout)
        
        # 分析變更
        resource_changes = plan_json.get("resource_changes", [])
        drift_details = []
        
        for change in resource_changes:
            if change["change"]["actions"] != ["no-op"]:
                drift_details.append({
                    "resource": change["address"],
                    "action": change["change"]["actions"],
                    "reason": "Drift detected"
                })
        
        return {
            "has_drift": True,
            "details": drift_details,
            "count": len(drift_details)
        }
    else:
        # 錯誤情況
        return {
            "has_drift": None,
            "details": f"Error checking drift: {result.stderr}"
        }

def send_drift_report(drift_result):
    if not drift_result["has_drift"]:
        return  # 無偏差,不傳送報告
    
    # 建立報告
    report = f"""
    Infrastructure Drift Report - {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
    
    Number of resources with drift: {drift_result['count']}
    
    Detailed Changes:
    """
    
    for item in drift_result["details"]:
        report += f"\n- Resource: {item['resource']}"
        report += f"\n  Action needed: {', '.join(item['action'])}"
        report += f"\n  Reason: {item['reason']}\n"
    
    # 傳送電子郵件報告
    msg = MIMEMultipart()
    msg['Subject'] = f"[ALERT] Infrastructure Drift Detected - {drift_result['count']} resources"
    msg['From'] = "infrastructure-monitor@example.com"
    msg['To'] = "infrastructure-team@example.com"
    
    msg.attach(MIMEText(report, 'plain'))
    
    with smtplib.SMTP('smtp.example.com') as server:
        server.send_message(msg)
    
    print(f"Drift report sent with {drift_result['count']} changes detected")

# 主執行流程
if __name__ == "__main__":
    drift_result = detect_terraform_drift()
    if drift_result["has_drift"]:
        send_drift_report(drift_result)
    else:
        print("No infrastructure drift detected")

這個Python指令碼實作了一個基礎設施偏差檢測系統。它使用Terraform的計劃功能來檢測實際基礎設施狀態與程式碼定義之間的差異。指令碼首先執行terraform plan命令並分析結束碼(0表示無變更,2表示有變更)。如果檢測到偏差,它會解析計劃JSON輸出以提取詳細的變更訊息,然後生成一個報告並透過電子郵件傳送給基礎設施團隊。這種自動化檢測可以幫助團隊快速識別和解決設定漂移問題,確保系統始終與程式碼定義保持一致。

結合不可變與可變方法

在實際環境中,純粹的不可變基礎設施可能並不總是可行。結合不可變和可變方法可以提供更大的靈活性,同時仍然保持設定一致性。

# 混合不可變和可變基礎設施的Terraform範例

# 不可變元件 - 透過AMI和啟動組態管理
resource "aws_ami_from_instance" "app_server" {
  name               = "app-server-${var.app_version}"
  source_instance_id = var.build_instance_id
  
  tags = {
    Name        = "AppServerAMI"
    Version     = var.app_version
    BuildDate   = timestamp()
    Environment = var.environment
  }
}

resource "aws_launch_configuration" "app_server" {
  name_prefix          = "app-server-${var.app_version}-"
  image_id             = aws_ami_from_instance.app_server.id
  instance_type        = var.instance_type
  security_groups      = [aws_security_group.app_server.id]
  iam_instance_profile = aws_iam_instance_profile.app_server.name
  
  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_autoscaling_group" "app_server" {
  name                 = "app-server-asg-${var.app_version}"
  launch_configuration = aws_launch_configuration.app_server.name
  min_size             = var.min_instances
  max_size             = var.max_instances
  vpc_zone_identifier  = var.subnet_ids
  
  lifecycle {
    create_before_destroy = true
  }
  
  tag {
    key                 = "Name"
    value               = "AppServer"
    propagate_at_launch = true
  }
  
  tag {
    key                 = "Version"
    value               = var.app_version
    propagate_at_launch = true
  }
}

# 可變元件 - 透過組態管理工具更新
resource "aws_instance" "database" {
  ami                    = var.database_ami
  instance_type          = var.database_instance_type
  subnet_id              = var.database_subnet_id
  vpc_security_group_ids = [aws_security_group.database.id]
  key_name               = var.ssh_key_name
  
  tags = {
    Name        = "Database"
    Environment = var.environment
    Managed     = "ansible"
  }
  
  # 使用null_resource和provisioner來應用組態管理
  provisioner "local-exec" {
    command = <<-EOT
      ansible-playbook \
        -i '${self.public_ip},' \
        -u ec2-user \
        --private-key=${var.ssh_private_key_path} \
        -e "db_version=${var.db_version}" \
        -e "environment=${var.environment}" \
        database-config.yml
    EOT
  }
  
  # 定期重新應用設定的null_resource
  resource "null_resource" "database_config_sync" {
    triggers = {
      db_version = var.db_version
      config_hash = filemd5("${path.module}/database-config.yml")
      # 增加時間觸發器以定期重新應用
      sync_time = formatdate("YYYY-MM-DD-hh-mm", timestamp())
    }
    
    provisioner "local-exec" {
      command = <<-EOT
        ansible-playbook \
          -i '${aws_instance.database.public_ip},' \
          -u ec2-user \
          --private-key=${var.ssh_private_key_path} \
          -e "db_version=${var.db_version}" \
          -e "environment=${var.environment}" \
          database-config.yml
      EOT
    }
  }
}

這個Terraform設定展示瞭如何結合不可變和可變基礎設施方法。對於應用伺服器,它使用不可變方法:從例項建立AMI,然後使用該AMI建立啟動設定和自動擴充套件組。當需要更新時,會建立新的AMI和啟動設定,然後更新自動擴充套件組,實作完全替換。

對於資料函式庫伺服器,它使用可變方法:維護長期執行的例項,並使用Ansible定期應用設定。null_resource資源使用多個觸發器來確保設定期重新應用,包括當設定檔案更改時、資料函式庫版本更改時,以及根據時間的觸發器以確保定期同步。

這種混合方法允許團隊為不同型別的基礎設施選擇最合適的管理策略:無狀態元件使用不可變方法以獲得最大的一致性和可靠性,而有狀態元件使用可變方法以保持資料完整性並避免不必要的重建。