微服務系統的 CI/CD

更快的釋出週期是微服務架構的一大優勢。 若沒有可靠的持續整合與持續交付(CI/CD)流程,微服務所帶來的敏捷性就會喪失。 本文概述微服務架構中常見的 CI/CD 挑戰,並建議獨立建置、驗證、保護及部署服務的方法。

什麼是 CI/CD?

CI/CD 指的是幾個相關流程:持續整合、持續交付與持續部署。

  • 持續整合(CI): 程式碼變更經常合併到主分支中。 自動化建置與測試流程確保主分支的程式碼始終達到生產品質。

  • 持續交付(CD): 通過 CI 流程的程式碼變更會自動發佈到類似生產環境。 部署到正式生產環境可能需要人工核准,但其他部分則是自動化的。 目標是讓你的程式碼隨時 準備好 部署到生產環境。

  • 持續部署: 通過前兩個步驟的程式碼變更會自動 部署到生產環境

請考慮微服務架構強固 CI/CD 程式的下列目標:

  • 每個小組都可以獨立建置及部署其擁有的服務,而不會影響或中斷其他小組。

  • 在新版本服務部署到生產環境前,先部署到開發/測試及品質保證環境進行驗證。 每個階段都會強制執行品質關卡。

  • 新版本的服務可以與舊版並存部署。

  • 已備妥足夠的訪問控制原則。 管線會使用聯邦式短期有效憑證向 Azure 驗證身分,而非使用長期機密。

  • 針對容器化工作負載,您可以信任部署到生產環境的容器映像。 這種信任是透過簽署映像、軟體物料清單(SBOM)證明,以及在流程中強制執行的漏洞掃描來建立的。

為什麼強固的 CI/CD 管線很重要

在傳統的單體應用程式中,單一建置管線會產生應用程式可執行檔。 所有開發工作都會匯入此管線。 若團隊發現高優先度錯誤,必須整合、測試並發布修正,這可能會延遲新功能的釋出。 你可以透過使用良好模組化的模組和功能分支,限制程式碼變更的影響,從而減少這些問題。 但隨著應用程式變得越來越複雜,且新增更多功能,單體式應用程式的發布流程往往也會變得更加複雜,且更可能失敗。

依照微服務的理念,絕不應該存在冗長的統一發布流程,讓所有團隊都得排隊配合。 建置服務 A 的團隊可以隨時發布更新,無需等待服務 B 的變更來合併、測試和部署。

比較單體式架構與微服務架構之間 CI/CD 的圖表。

若要達到高發行速度,您的發行管線必須自動化且高度可靠,才能將風險降至最低。 如果您每天部署至生產環境一次或多次,則回歸或服務中斷現象必須罕見。 同時,如果你部署了有問題的更新,就必須有可靠的方法,能夠快速回滾或向前推進到服務的先前版本。

Challenges

  • 許多小型獨立程式碼庫: 每個團隊負責建立自己的服務,並擁有自己的建置流程。 在某些組織中,團隊可能會使用獨立的程式碼庫。 分開的資料庫可以分散如何建置系統的知識到不同團隊。 因此,組織中沒有人知道如何部署整個應用程式。

    緩解措施: 建立統一且自動化的管線,或至少共用的管線基礎設施來建置與部署服務,避免這些知識隱藏在每個團隊中。 可重複使用的管線範本,如 GitHub Actions可重用工作流程Azure Pipelines模板,有助於標準化建置、測試、掃描及部署各項服務的步驟。

  • 多語言與框架: 每個團隊使用自己的技術組合,因此要建立一個能跨工作負載運作的單一建置流程可能很困難。 建置過程必須足夠彈性,讓每個團隊都能根據所選語言或框架進行調整。

    緩解措施: 將每個服務的建置流程容器化,讓建置系統只需執行容器。 像 GitHub Actions、Azure Pipelines 和 Azure Container Registry tasks 這類平台,無論原始語言為何,都能一致建立並發布容器映像檔。

  • 整合與負載測試: Teams 以自己的節奏發布更新,因此設計穩健的端到端測試具有挑戰性,尤其當服務依賴其他服務時。 運行完整的生產叢集成本可能很高,因此不太可能每個團隊都只在生產規模上運行完整的叢集來測試。

    緩解措施:使用短暫的預覽環境,例如 Kubernetes 中每個拉取請求各自的命名空間,或按需建立的 Azure 容器應用程式 環境。 使用合約測試,能及早發現整合問題,而不必全面複製生產環境。

  • 發佈管理: 每個團隊都應該能夠部署更新到生產環境。 這項要求並不代表每位團隊成員都有部署權限。 集中式的發佈管理員角色可能會降低部署速度。

    緩解措施: 你的 CI/CD 流程越自動化且可靠,就越不需要中央權威。 你可能還是會對重大功能更新和小錯誤修正有不同的政策。 去中心化的做法並不代表沒有治理。 使用 Azure Pipelines 環境與核准GitHub Actions 部署環境與必要檢閱者 來強制執行核准流程,並使用 適用於 Azure Kubernetes Service (AKS) 的 Azure 原則OPA Gatekeeper,以程式碼定義叢集端原則。

  • 服務更新: 當你將服務更新到新版本時,更新不應該會導致依賴該更新的其他服務失敗。

    緩解措施: 對於非破壞性的變更,可以使用像是藍綠或金絲雀釋出等部署技術。 若要進行重大 API 變更,請與舊版並存部署新版本。 透過此方法,使用先前 API 的服務可以更新並測試以適應新 API。 欲了解更多資訊,請參閱 更新服務

  • 管線身份與秘密管理: 儲存在管線中的長期服務主體機密,經常是入侵與操作工作的來源。 服務主體秘密會過期、可能外洩,且需要在多個獨立的微服務管線間輪替。

    緩解措施: 使用工作負載身分同盟將管線驗證到 Azure,此機制使用 OpenID Connect(OIDC),因此不必在管線中儲存用戶端密碼。 如需詳細資訊,請參閱 Azure Pipelines 的工作負載身分識別在 Azure 中為 GitHub Actions 設定 OpenID Connect。 將剩餘的秘密存放在 Azure Key Vault,並在執行時參考它們。

  • 供應鏈安全: 你交付給生產環境的所有東西都必須能追溯到它所建構的程式碼和相依性。 微服務會增加映像檔、登錄檔和管線的數量,進而擴大供應鏈攻擊面。

    Mitigation: 使用 Notation 和 金鑰保存庫 簽署容器影像,並在進入時使用 AKS 影像完整性Ratify 來驗證簽章。 產生 SBOM 作為建置產物。 透過使用 適用於雲端的 Microsoft Defender DevOps security 以及 GitHub Advanced Security掃描程式碼、相依性與管線。 使用 Microsoft Defender for Containers 掃描執行時影像。 必須在所有掃描都通過後,才能進行發布。

Monorepo 與 multirepo

在建立 CI/CD 工作流程之前,您必須了解程式碼庫的結構與管理,包括:

  • 無論團隊是在各自獨立的存放庫中工作,還是在單一存放庫中工作。
  • 你的分支策略。
  • 誰可以將程式碼推送到生產環境,以及是否有發行管理器存在。

團隊在生產環境中廣泛使用這兩種方法。 你的選擇取決於團隊拓撲、工具成熟度,以及跨服務共享的程式碼量。

  單一倉庫 多個存放庫
優點 - 程式碼共享

- 更容易標準化程式碼與工具

- 更容易重構程式碼

- 可發現性(程式碼的單一視圖)
- 明確的每隊所有權

- 潛在的合併衝突減少

- 協助強制微服務解耦
挑戰 - 對共享程式碼的變更可能影響多個微服務

- 更有可能發生合併衝突

- 工具必須可擴展至大型程式碼庫

- 存取控制

- 部署流程更複雜
- 程式碼分享較困難

- 編碼標準較難執行

- 相依性管理

- 程式碼基礎分散,發現性差

- 缺乏共享基礎設施

不論您選擇哪一種模型,請在您的管線中使用路徑範圍觸發條件,例如 GitHub Actions 中的 path filtersAzure Pipelines 中的 trigger paths。 以路徑為範圍的觸發條件有助於確保只有受影響的微服務才會在每次提交時重新建置並重新部署。

更新服務

對於已在正式環境中運行的服務,可採用多種更新策略,包括滾動更新、藍綠部署和金絲雀發布。 這些模式通常透過 GitOps 工作流程來協調。 欲了解更多資訊,請參閱 GitOps 與漸進式交付

輪流更新

在滾動更新中,你會部署新的服務實例,這些新實例會立即開始接收請求。 當新實例準備好時,先前的實例會被移除。

Kubernetes 範例:在 Kubernetes 中,滾動更新是更新部署 Pod 規格時的預設行為。 部署控制器會為已更新的 Pod 建立新的 ReplicaSet。 接著它會放大新的 ReplicaSet,同時縮小前一個 ReplicaSet,以維持所需的複本數量。 它不會刪除之前的 Pod,直到新 Pod 準備好。 Kubernetes 會保留更新的歷程記錄,因此您可以視需要回復更新。

容器應用程式範例: 容器應用程式使用 修訂 來管理滾動更新。 當你部署新版本時,容器應用程式可以透過流量分割規則,逐步將流量從前一個版本轉移到新版本。 如果新的修訂版本發生問題,你可以將流量導回上一個修訂版本來回滾。 你可以同時設定多個活躍版本,並控制每個版本接收的流量百分比。

滾動更新的一項挑戰是,在更新過程中,舊版和新版會同時運行,並接收流量。 在此期間,系統可將任何請求路由至任一版本。

對於重大 API 變更,最佳做法是同時支援這兩個版本,直到更新舊版的所有客戶端為止。 欲了解更多資訊,請參閱 API 版本管理

藍綠部署

在藍綠部署中,您會與舊版一起部署新版本。 驗證新版本之後,您會一次將所有流量從舊版切換至新版本。 切換之後,您可以監視應用程式是否有任何問題。 如果有問題,你可以將流量切換回先前的版本。 如果沒有問題,你可以刪除之前的版本。

對於較傳統的單體或 N 層應用,藍綠部署通常意味著你會建立兩個相同的環境。 你先將新版本部署到預備環境,然後透過交換虛擬 IP 位址,將客戶端流量重新導向該環境。 在微服務架構中,更新發生在微服務層級,因此通常會將更新部署到相同環境中,並利用服務發現機制切換流量。

Kubernetes 範例:在 Kubernetes 中,你不需要建立獨立叢集來做藍綠部署。 相反地,您可以利用選取器。 建立新的 Deployment 資源,搭配新的 Pod 規格和一組不同的標籤。 建立這個部署,但不要刪除之前的部署,也不要修改指向它的服務。 新 pod 運行後,你可以更新服務的選擇器以符合新部署。

藍綠部署的一個缺點是,在更新期間,服務會運行兩倍數量的 pod(目前版本和下一個版本)。 如果 pod 佔用大量 CPU 或記憶體資源,你可能需要暫時擴展叢集以滿足較高的資源需求。

金絲雀測試版本

在 Canary 版本中,你會先向少數客戶端部署更新版本,然後監控新服務的行為,然後再推送到所有客戶端。 透過這種方法,你可以以可控的方式逐步推出,監測實際數據,並在問題影響所有客戶之前找出問題。

Canary 版本比藍綠更新或滾動更新更複雜,因為你必須動態地將請求路由到不同版本的服務。

Kubernetes 範例:在 Kubernetes 中,你可以設定服務跨越兩個副本集(每個版本各一個),並手動調整副本數量。 然而,這種方法較為粗略,因為 Kubernetes 會在各個 Pod 之間進行負載平衡。 例如,如果您總共有10個副本,則只能以10%的增量來轉移流量。 如果你使用服務網狀結構,可以使用服務網狀路由規則來實作更複雜的金絲雀釋放策略。

Container Apps 中的範例: 在 Container Apps 中,你可以使用 流量分割,將指定比例的流量傳送到新的修訂版本(例如將 10% 的流量導向 v2,而 90% 仍維持在 v1),並隨著信心增加調整權重,且不需要外部服務網格。

漸進式交付與 GitOps

對於在 Kubernetes 上操作多個微服務的團隊來說,基於 GitOps 的拉取模型可以補充早期的推送範例。 想要的叢集狀態存在於 Git 中,叢集內的運算子會將叢集與該狀態進行調和。 CI 負責建置、測試、掃描、簽署並推送映像。 CD 會使叢集狀態與資訊清單一致。 這種分離讓你有審計追蹤和更輕鬆的災難復原(DR)。 這也使 CI 執行器不再需要直接持有叢集憑證。

下一步