Terraform 與 CloudFormation:權威指南

已發表: 2022-03-11

如果您像我一樣在互聯網上搜索以幫助您在 CloudFormation 和 Terraform 作為您的下一個基礎架構即代碼 (IaC) 工具之間進行選擇,但沒有找到明確的答案,那麼我很長一段時間都在分享您的痛苦。 現在,我對這兩種工具都有豐富的經驗,我可以就使用哪一種做出明智的決定。

TL;博士

對於 AWS 上的 IaC 項目,請選擇 CloudFormation,因為:

  1. CloudFormation 對代碼(即模板)和代碼的實例化(即堆棧)進行了區分。 在 Terraform 中,沒有這樣的區別。 下一節將對此進行更多介紹。
  2. Terraform 不能很好地處理基本的依賴管理。 更多內容將在後面的部分中介紹。

區分代碼和實例化

CloudFormation 和 Terraform 之間的一個區別是代碼和實例在每個服務中如何相互關聯。

CloudFormation 具有堆棧的概念,即模板的實例化。 同一模板可以由給定客戶在給定帳戶中、跨帳戶或由不同客戶無限地實例化。

Terraform 沒有這樣的概念,並且要求代碼與其實例化之間存在一對一的關係。 這類似於為每個要運行的服務器複製 Web 服務器的源代碼,或者每次需要運行應用程序而不是運行編譯版本時都複製代碼。

這一點在簡單設置的情況下是相當微不足道的,但它很快成為中大型運營的主要痛點。 在 Terraform 中,每次需要從現有代碼中創建新堆棧時,都需要復制代碼。 複製/粘貼腳本文件是一種非常簡單的方法來破壞自己並破壞您不打算接觸的資源。

Terraform 實際上沒有 CloudFormation 之類的堆棧概念,這清楚地表明 Terraform 是從頭開始構建的,以便在代碼與其管理的資源之間進行一對一的匹配。 後來,環境的概念(後來被重命名為“工作區”)部分糾正了這一點,但是使用這些的方式使得部署到不需要的環境非常容易。 這是因為您必須在部署之前運行terraform workspace select ,並且忘記此步驟將部署到先前選擇的工作區,這可能是您想要的,也可能不是。

在實踐中,使用 Terraform 模塊確實可以緩解這個問題,但即使在最好的情況下,您也需要大量的樣板代碼。 事實上,這個問題非常嚴重,以至於人們需要圍繞 Terraform 創建一個包裝工具來解決這個問題:Terragrunt。

狀態管理和權限

CloudFormation 和 Terraform 之間的另一個重要區別是它們各自管理狀態和權限的方式。

CloudFormation 為您管理堆棧狀態,並且不為您提供任何選項。 但根據我的經驗,CloudFormation 堆棧狀態一直很穩定。 此外,CloudFormation 允許權限較低的用戶管理堆棧,而無需擁有堆棧本身所需的所有必要權限。 這是因為 CloudFormation 可以從附加到堆棧的服務角色獲取權限,而不是從運行堆棧操作的用戶獲取權限。

Terraform 要求您為其提供一些後端來管理狀態。 默認是一個本地文件,這完全不能令人滿意,因為:

  1. 狀態文件的健壯性與存儲它的機器的健壯性完全相關。
  2. 這幾乎使團隊合作變得不可能。

所以你需要一個健壯和共享的狀態,在 AWS 上這通常是通過使用 S3 存儲桶來存儲狀態文件來實現的,並伴隨著一個 DynamoDB 表來處理並發。

這意味著您需要為要實例化的每個堆棧手動創建 S3 存儲桶和 DynamoDB 表,還需要手動管理這兩個對象的權限,以限制權限較低的用戶訪問他們不應訪問的數據。 如果你只有幾個堆棧,那不會有太大問題,但如果你有 20 個堆棧要管理,那確實變得非常麻煩。

順便說一句,在使用 Terraform 工作區時,每個工作區不可能有一個 DynamoDB 表。 這意味著,如果您希望 IAM 用戶具有最小權限來執行部署,該用戶將能夠擺弄所有工作區的鎖,因為 DynamoDB 權限沒有細粒度到項目級別。

依賴管理

在這一點上,CloudFormation 和 Terraform 都可能有點棘手。 如果您更改資源的邏輯 ID(即名稱),兩者都會認為必須銷毀舊資源並創建新資源。 因此,在任一工具中更改資源的邏輯 ID 通常不是一個好主意,尤其是對於 CloudFormation 中的嵌套堆棧。

如第一節所述,Terraform 不處理基本依賴項。 不幸的是,儘管明顯缺乏變通方法,Terraform 開發人員並沒有對這個長期存在的問題給予太多關注。

鑑於適當的依賴管理對於 IaC 工具絕對至關重要,一旦涉及到關鍵業務操作(例如部署到生產環境),Terraform 中的此類問題就會對其適用性提出質疑。 CloudFormation 給人一種更專業的感覺,AWS 總是非常注意確保它為客戶提供生產級工具。 在我使用 CloudFormation 的這些年裡,我從來沒有遇到過依賴管理的問題。

CloudFormation 允許堆棧導出它的一些輸出變量,然後可以被其他堆棧重用。 老實說,此功能是有限的,因為您將無法在每個區域實例化多個堆棧。 這是因為您不能導出兩個具有相同名稱的變量,並且導出的變量沒有命名空間。

Terraform 不提供此類設施,因此您的選擇較少。 Terraform 允許您導入另一個堆棧的狀態,但這使您可以訪問該堆棧中的所有信息,包括存儲在該狀態中的許多秘密。 或者,堆棧可以以存儲在 S3 存儲桶中的 JSON 文件的形式導出一些變量,但同樣,此選項更麻煩:您必須決定使用哪個 S3 存儲桶並為其授予適當的權限,然後編寫所有在作者和讀者方面自己編寫代碼。

Terraform 的一個優點是它具有數據源。 因此,Terraform 可以查詢不受 Terraform 管理的資源。 但是,在實踐中,當您想要編寫通用模板時,這幾乎沒有關係,因為您不會假設任何關於目標帳戶的內容。 CloudFormation 中的等價物是添加更多的模板參數,因此涉及重複和潛在的錯誤; 但是,根據我的經驗,這從來都不是問題。

回到 Terraform 的依賴管理問題,另一個例子是當你嘗試更新負載均衡器的設置時出現錯誤並得到以下信息:

 Error: Error deleting Target Group: ResourceInUse: Target group 'arn:aws:elasticloadbalancing:us-east-1:723207552760:targetgroup/strategy-api-default-us-east-1/14a4277881e84797' is currently in use by a listener or a rule status code: 400, request id: 833d8475-f702-4e01-aa3a-d6fa0a141905

預期的行為是 Terraform 檢測到目標組是某些其他未刪除資源的依賴項,因此,它不應該嘗試刪除它,但也不應該拋出錯誤。

運營

儘管 Terraform 是一個命令行工具,但很明顯它需要人類來運行它,因為它非常具有交互性。 可以在批處理模式下(即從腳本)運行它,但這需要一些額外的命令行參數。 考慮到 IaC 工具的目的是自動化,Terraform 被開發為默認由人類運行的事實非常令人費解。

Terraform 很難調試。 錯誤消息通常非常基本,無法讓您了解出了什麼問題,在這種情況下,您必須使用TF_LOG=debug運行 Terraform,這會產生大量輸出以進行拖網。 更複雜的是,Terraform 有時對 AWS 的 API 調用失敗,但失敗不是 Terraform 的問題。 相比之下,CloudFormation 提供了相當清晰的錯誤消息,其中包含足夠的詳細信息,可以讓您了解問題所在。

一個示例 Terraform 錯誤消息:

 Error: error reading S3 bucket Public Access Block: NoSuchBucket: The specified bucket does not exist status code: 404, request id: 19AAE641F0B4AC7F, host id: rZkgloKqxP2/a2F6BYrrkcJthba/FQM/DaZnj8EQq/5FactUctdREq8L3Xb6DgJmyKcpImipv4s=

上面的錯誤消息顯示了一個明確的錯誤消息,它實際上並沒有反映潛在的問題(在這種情況下是權限問題)。

此錯誤消息還顯示 Terraform 有時如何將自己繪製到角落。 例如,如果您在該存儲桶上創建了一個 S3 存儲桶和一個aws_s3_bucket_public_access_block資源,並且由於某種原因您在銷毀該存儲桶的 Terraform 代碼中進行了一些更改——例如,在上述“更改意味著刪除和創建”陷阱中—— Terraform 將在嘗試加載aws_s3_bucket_public_access_block時卡住,但由於上述錯誤而不斷失敗。 Terraform 的正確行為是酌情替換或刪除aws_s3_bucket_public_access_block

最後,您不能將 CloudFormation 幫助程序腳本與 Terraform 一起使用。 這可能會讓人煩惱,尤其是當您希望使用 cfn-signal 時,它會告訴 CloudFormation EC2 實例已完成自身初始化並準備好處理請求。

語法、社區和回滾

在語法方面,與 CloudFormation 相比,Terraform 確實有一個很好的優勢——它支持循環。 但根據我自己的經驗,這個功能可能會有點危險。 通常,循環將用於創建許多相同的資源; 但是,當您想用不同的計數更新堆棧時,您可能需要鏈接舊資源和新資源(例如,使用zipmap()組合兩個數組中的值,而這兩個數組現在恰好是大小不同,因為一個數組具有舊循環大小的大小,另一個具有新循環大小的大小)。 確實,這樣的問題可以在沒有循環的情況下發生,但是如果沒有循環,問題對於編寫腳本的人來說會更加明顯。 在這種情況下使用循環會混淆問題。

Terraform 的語法還是 CloudFormation 的語法更好,主要是偏好問題。 CloudFormation 最初只支持 JSON,但 JSON 模板很難閱讀。 幸運的是,CloudFormation 還支持 YAML,它更易於閱讀並允許註釋。 不過,CloudFormation 的語法往往相當冗長。

Terraform 的語法使用 HCL,它是 JSON 的一種衍生物,非常特殊。 Terraform 提供了比 CloudFormation 更多的功能,而且它們通常更容易理解。 所以可以說 Terraform 在這一點上確實有一點優勢。

Terraform 的另一個優勢是其現成的社區維護模塊集,這確實簡化了模板的編寫。 一個問題可能是此類模塊可能不夠安全,無法滿足組織的要求。 因此,對於需要高度安全性的組織來說,審查這些模塊(以及它們出現的更多版本)可能是必要的。

一般來說,Terraform 模塊比 CloudFormation 嵌套堆棧靈活得多。 CloudFormation 嵌套堆棧傾向於隱藏其下方的所有內容。 從嵌套堆棧中,更新操作將顯示嵌套堆棧將被更新,但不會詳細顯示嵌套堆棧內將發生什麼。

最後一點,實際上可能是有爭議的,是 CloudFormation 嘗試回滾失敗的部署。 這是一個非常有趣的功能,但不幸的是可能會很長(例如,CloudFormation 可能需要長達三個小時才能確定部署到 Elastic Container Service 失敗)。 相反,在失敗的情況下,Terraform 只是停止在原來的位置。 回滾功能是否是一件好事是有爭議的,但我已經開始欣賞這樣一個事實,即當更長的等待恰好是可以接受的權衡時,堆棧會盡可能地保持在工作狀態。

為 Terraform 與 CloudFormation 辯護

Terraform 確實比 CloudFormation 具有優勢。 在我看來,最重要的一點是,在應用更新時,Terraform 會向您顯示您將要進行的所有更改,包括深入了解它正在使用的所有模塊。 相比之下,CloudFormation 在使用嵌套堆棧時僅向您顯示嵌套堆棧需要更新,但不提供深入了解細節的方法。 這可能令人沮喪,因為在點擊“開始”按鈕之前了解此類信息非常重要。

CloudFormation 和 Terraform 都支持擴展。 在 CloudFormation 中,可以使用您自己創建的 AWS Lambda 函數作為後端來管理所謂的“自定義資源”。 對於 Terraform,擴展更容易編寫並成為代碼的一部分。 所以在這種情況下,Terraform 有一個優勢。

Terraform 可以處理許多雲供應商。 這使 Terraform 能夠在多個雲平台之間統一給定部署。 例如,假設您在 AWS 和 Google Cloud Platform (GCP) 之間分配了一個工作負載。 通常,工作負載的 AWS 部分將使用 CloudFormation 進行部署,而 GCP 部分將使用 GCP 的 Cloud Deployment Manager。 使用 Terraform,您可以改為使用單個腳本在各自的雲平台中部署和管理兩個堆棧。 這樣,您只需部署一個堆棧而不是兩個。

Terraform 與 CloudFormation 的非參數

有相當多的非爭論繼續在互聯網上流傳。 最大的問題是,由於 Terraform 是多雲的,您可以使用一個工具來部署所有項目,無論它們在什麼雲平台上完成。 從技術上講,這是對的,但這並不是它看起來的大優勢,尤其是在管理典型的單云項目時。 現實情況是,在(例如)CloudFormation 中聲明的資源與在 Terraform 腳本中聲明的相同資源之間幾乎一一對應。 由於無論哪種方式都必須了解特定於雲的資源的詳細信息,因此差異歸結為語法,這幾乎不是管理部署的最大痛點。

有人認為,通過使用 Terraform,可以避免供應商鎖定。 這個論點不成立,因為使用 Terraform,你被 HashiCorp(Terraform 的創建者)鎖定,就像使用 CloudFormation,你被 AWS 鎖定一樣,對於其他雲,依此類推平台。

Terraform 模塊更容易使用這一事實對我來說並不重要。 首先,我認為 AWS 有意避免為基於社區的 CloudFormation 模板託管單個存儲庫,因為人們認為這對用戶製造的安全漏洞和違反各種合規計劃負有責任。

在更個人的層面上,我完全理解在軟件開發中使用庫的好處,因為這些庫很容易運行到數万行代碼中。 然而,在 IaC 的情況下,代碼的大小通常要小得多,這樣的模塊通常只有幾十行。 從某種意義上說,使用複制/粘貼實際上並不是一個壞主意,因為它避免了維護兼容性和將您的安全委託給不知名的人的問題。

許多開發人員和 DevOps 工程師不贊成使用複制/粘貼,這背後有充分的理由。 但是,我的觀點是,對代碼片段使用複制/粘貼可以讓您輕鬆地根據需要對其進行定制,並且無需將其製作成庫並花費大量時間使其通用。 維護這些代碼片段的痛苦通常很小,除非您的代碼在十幾個或更多模板中重複出現。 在這種情況下,挪用代碼並將其用作嵌套堆棧是有意義的,並且不重複自己的好處可能大於執行更新時無法看到嵌套堆棧內將要更新的內容的煩惱手術。

CloudFormation 與 Terraform 結論

借助 CloudFormation,AWS 希望為其客戶提供堅如磐石的工具,該工具將始終按預期工作。 當然,Terraform 的團隊也這樣做了——但不幸的是,他們工具的一個關鍵方面,依賴管理似乎不是優先考慮的。

Terraform 可能會在您的項目中佔有一席之地,尤其是在您擁有多雲架構的情況下,在這種情況下,Terraform 腳本是統一您使用的各種雲供應商的資源管理的一種方式。 但是在這種情況下,您仍然可以通過僅使用 Terraform 來管理已經使用其各自的特定於雲的 IaC 工具實現的堆棧來避免 Terraform 的缺點。

Terraform 與 CloudFormation 的總體感覺是,CloudFormation 雖然不完美,但更專業和可靠,我絕對會推薦它用於任何不是特別多雲的項目。