Terraform AWS 雲:健全的基礎設施管理
已發表: 2022-03-11編寫應用程序只是故事的一部分。 為了使其有價值,它需要部署在可以擴展的地方,它必須以高可用性運行,它需要備份等等。
越來越多的開發人員需要至少掌握這個部署過程。 這表現為,例如,隨著系統複雜性的增加,DevOps 成為當今經常需要的角色。 我們不能讓自己忽視這些轉變,需要了解如何設計易於部署的應用程序。
這也符合我們客戶的利益:他們聘請我們作為我們領域的專家,並希望我們提供整個產品,通常是從頭到尾。 他們有需求,並且經常忽略他們的業務解決方案運行的堆棧。 最後,重要的是產品的商業價值。
介紹 Terraform
部署和基礎設施管理不是一個簡單的過程。 除了許多不斷變化的領域專業知識之外,我們還需要學習另一種工具或新的工作流程。 如果您一直推遲此操作,那麼本文是您了解一種基礎架構管理方法的機會。 我希望你最終會對使用 Terraform 更有信心,並更多地了解可能的方法和挑戰。 您應該能夠使用此工具至少開始管理您的雲基礎架構的某些部分。
Terraform 是一個抽象層,是的,而且抽像是有漏洞的,我同意。 但最終,我們從事的是解決問題和管理抽象的業務。 這旨在讓我們在日常工作中更加清醒。
目標
我將解釋 Terraform 是什麼,它如何適應整個生態系統,以及它與其他類似工具的比較。 然後,我將向您展示為團隊配置多環境和生產就緒的 Terraform 設置所需的步驟。 我將解釋編寫 Terraform 配置的基礎知識——如何使用可共享模塊管理複雜性和重複代碼。
這些示例都將集中在一個雲提供商上:亞馬遜網絡服務 (AWS)。 這只是我最有經驗的雲,但所有信息也應該適用於其他雲。
我將以一些我希望在開始時就知道的註釋結束:一些語法陷阱、怪癖以及 Terraform 不是我選擇的工具的情況。
我不會關注語法的細節。 通過閱讀 HashiCorp 為 Terraform 提供的精彩文檔和指南,您可以快速了解這一點。 我想專注於從一開始可能並不明顯的事情,我希望我在開始使用 Terraform 之前就知道這些事情。 希望這將為您指明正確的方向,並允許您將 Terraform 視為在未來項目中使用的工具。
基礎設施管理很難
為雲中的應用程序設置環境涉及多個步驟。 除非你把它們都寫成詳細的清單並密切關注它們,否則你會一直犯錯; 畢竟,我們是人類。 這些步驟很難分享。 您需要記錄大量手動程序,並且文檔很快就會過時。 現在將所有這些乘以單個應用程序的環境總數: dev 、 test/qa 、 stage和prod 。 您還需要考慮每個人的安全性。
不是每個工具都有一個我可以使用並忘記複雜性的 UI 嗎?
他們確實有 UI——AWS 管理控制台就是一個典型的例子。 但是這些工具在幕後做了很多事情。 在 UI 上單擊一次實際上可能會引發一連串難以掌握的變化。 通常無法撤消您在 UI 中所做的操作(通常的“您確定嗎?”提示通常是不夠的)。 此外,擁有第二雙眼睛來檢查更改總是一個好主意,但是當我們使用 UI 時,我們需要與此人坐在一起或在更改完成後檢查我們的更改,這更像是審核而不是審查。 每個雲提供商都有自己的 UI,您需要掌握這些 UI。 UI 被設計為易於使用(不一定簡單!),因此容易產生妄想方法,例如“這只是一個微小的調整”,或者您會在 48 小時內忘記的快速生產修補程序。 這種手動點擊也很難自動化。
CLI 工具呢?
對於我們的用例,它們會比 UI 工具更好。 但是,您仍然傾向於手動進行更改或編寫很容易失控的 bash 腳本。 此外,每個提供商都有自己的 CLI 工具。 使用這些工具,您在提交之前無法看到您的更改。 值得慶幸的是,這不是一個新問題,並且有一些工具可以幫助解決這個問題。
編排與配置管理
我將定義一些用於管理基礎設施的工具和實踐類別。 這些是編排和配置管理。 您可以將它們大致視為聲明式和命令式編程模型(想想 Prolog 與 Python)。 每個人都有自己的優點和缺點,但最好了解它們並為給定的工作應用最好的工具。 此外,編排通常涉及比配置管理更高的抽象級別。
編排
編排更像是一種聲明式編程範式。 我喜歡把它想像成管弦樂隊的指揮。 指揮家的工作被維基百科(鏈接)很好地總結為“通過使用手勢來指揮多個演奏者或歌手同時表演的藝術”。 指揮傳達音樂的節拍和節奏、動態和提示,但實際工作是由擅長演奏樂器的個別音樂家執行的。 沒有這樣的協調,他們就無法演奏出完美的作品。
這就是 Terraform 適合的地方。 您使用它來執行一個 IT 基礎架構:您告訴它要部署什麼,然後 Terraform 將它們鏈接在一起並執行所有必要的 API 調用。 這個領域有類似的工具; 最著名的之一是 AWS Cloud Formation。 與 Terraform 相比,它對恢復和回滾有更好的支持,但在我看來,它的學習曲線更陡峭。 它也與云無關:它僅適用於 AWS。
配置管理
這些實踐的補充方面是配置管理方法。 在此範例中,您指定工具必須執行的確切步驟以達到給定的所需配置。 這些步驟本身可能很小並且支持多個操作系統,但您需要積極考慮它們的執行順序。 他們對當前狀態和環境一無所知(除非您對其進行編程),因此,他們會盲目地執行您給他們的任何步驟。 這可能會導致稱為配置漂移的問題,您的資源將慢慢與它們最初打算表示的內容不同步,特別是如果您對它們進行了一些手動更改。 他們擅長在單個實例上管理和配置服務。 擅長此工作流的工具示例包括 Chef、Puppet、Ansible 和 Salt。
編排對您的基礎架構實施了一種方法,您將資源視為牛,而不是寵物。 當出現問題時,您無需手動“培養”每個 VPS,而是可以用精確的副本替換它們。 我並不是說你根本不關心並重新開始希望最好的事情。
相反,您應該調查並修復代碼中的問題,然後部署它。
Ansible(和其他 CM 工具)可用於管理 AWS 基礎設施,但這會涉及大量工作並且更容易出錯,尤其是當基礎設施經常更改且複雜性增加時。
要記住的一件重要事情是編排和配置管理方法不會相互衝突。 它們是兼容的。 在由 Terraform 管理的 AutoScaling 組中擁有一組 EC2 (VPS) 實例是完全可以的,但運行 AWS 應用程序映像 (AMI),它是磁盤的快照,它是通過諸如 Ansible 之類的命令性步驟準備的. Terraform 甚至有一個“提供者”的概念,一旦機器啟動,您就可以運行外部的供應工具。
Terraform 文檔很好地解釋了這一點,並幫助您將 Terraform 置於整個生態系統中。
什麼是 Terraform?
它是一個開源工具,由 HashiCorp 創建,允許您將基礎架構編碼為聲明性配置文件,這些文件是版本化和共享的,並且可以查看。
HashiCorp 的名字應該響起——他們也叫 Nomad、Vault、Packer、Vagrant 和 Consul。 如果您使用過任何這些工具,那麼您已經了解文檔的質量、充滿活力的社區以及您可以從他們的解決方案中獲得的有用性。
基礎設施即代碼
Terraform 與平台無關; 您可以使用它來管理裸機服務器或云服務器,例如 AWS、Google Cloud Platform、OpenStack 和 Azure。 在 Terraform 術語中,這些被稱為提供者,您可以通過閱讀支持的提供者的完整列表來了解規模。 可以同時使用多個提供者,例如,提供者 A 為您配置 VM,但提供者 B 配置和委託 DNS 記錄。
這是否意味著只需更改配置文件即可切換雲提供商? 不,我什至不認為你會想要那個,至少不是以自動化的方式。 問題是不同的提供者可能有不同的能力、不同的產品、流程、想法等。這意味著你將不得不為不同的提供者使用不同的資源來表達相同的概念。 但是,這仍然可以通過單一、熟悉的配置語法來完成,並且是一個有凝聚力的工作流程的一部分。
Terraform 設置的基本部分
- Terraform 二進製文件本身,您必須安裝
- 源代碼文件,即您的配置
- 表示 Terraform 管理的資源的狀態(本地或遠程)(稍後會詳細介紹)
編寫 Terraform 配置
您可以使用 HCL 語言在*.tf
文件中編寫 Terraform 配置代碼。 可以選擇使用 JSON 格式 ( *.tf.json
),但它針對的是機器和自動生成,而不是人類。 我建議您堅持使用 HCL。 我不會深入研究 HCL 語言的語法; 官方文檔在描述如何編寫 HCL 以及如何使用變量和插值方面做得非常出色。 我只會提到理解示例所需的最低限度。
在 Terraform 文件中,您主要處理資源和數據源。 資源代表您的基礎設施的組件,例如,AWS EC2 實例、RDS 實例、Route53 DNS 記錄或安全組中的規則。 它們允許您在雲架構中配置和更改它們。
假設您已設置 Terraform,如果您發出terraform apply
,下面的代碼將創建一個功能齊全的 EC2 實例(僅顯示某些屬性):
resource "aws_instance" "bastion" { ami = "ami-db1688a2" # Amazon Linux 2 LTS Candidate AMI 2017.12.0 (HVM), SSD Volume Type - ami-db1688a2 instance_type = "t2.nano" key_name = "${var.key_name}" subnet_ vpc_security_group_ids = ["${aws_security_group.bastion.id}"] monitoring = "false" associate_public_ip_address = "true" disable_api_termination = "true" tags = { Name = "${var.project_tag}-bastion-${var.env}" Env = "${var.env}" Application ApplicationRole = "Bastion Host" Project = "${var.project_tag}" } }
另一方面,有些數據源允許您在不更改給定組件的情況下讀取有關給定組件的數據。 您想獲取 ACM 頒發的證書的 AWS ID (ARN)? 您使用數據源。 不同之處在於,在配置文件中引用數據源時,數據源以data_
為前綴。
data "aws_acm_certificate" "ssl_cert" { domain = "*.example.com" statuses = ["ISSUED"] }
以上引用了可與 AWS ALB 一起使用的已頒發 ACM SSL 證書。 在你做這一切之前,你需要設置你的環境。
文件夾結構
Terraform 環境(及其狀態)由目錄分隔。 Terraform 將目錄中的所有*.tf
文件加載到一個命名空間中,因此順序無關緊要。 我推薦以下目錄結構:
/terraform/ |---> default_variables.tf (1) /stage/ (2) |---> terraform.tfvars (3) |---> default_variables.tf (4) |---> terraform.tf (5) |---> env_variables.tf (6) /prod/ /<env_name>/
-
default_variables.tf
– 定義所有頂級變量和可選的默認值。 它們可以通過符號鏈接在每個環境(嵌套目錄)中重複使用。 -
/stage/
- 一個保存整個獨立環境配置的目錄(這裡命名為stage
,但它可以是任何東西)。 在此文件夾中所做的任何更改都完全獨立於其他環境(env-likeprod
),這是您想要的,以避免因對stage
所做的更改而弄亂生產環境! -
terraform.tfvars
– 定義變量值。.tfvars
文件與.env
文件類似,因為它們保存已定義變量的key=val
對。 例如,這指定了我使用的 AWSprofile
、AWSkey_name
和 AWSkey_path
。 在 Git 中可以忽略。 -
default_variables.tf
– 這是文件 (2) 的符號鏈接,它允許我們共享與環境無關的變量而無需重複自己。 -
terraform.tf
- 這是每個 env 的主要配置; 它包含配置後端的terraform {}
塊。 我也在此處配置提供程序。 -
env_variables.tf
- 該文件包含特定於 env 的變量。 我在 AWS 中使用Env=<env_name>
標記所有資源,因此該文件通常只定義一個變量:env
。
當然,這不是構建環境的唯一方法。 通過實現關注點的明確分離,這對我來說效果很好。
後端配置
我已經提到了 Terraform 狀態。 這是 Terraform 工作流程的重要組成部分。 您可能想知道 state 是否真的是必需的。 Terraform 不能一直查詢 AWS API 以獲取基礎設施的實際狀態嗎? 好吧,如果您考慮一下,Terraform 需要維護它在聲明性配置文件中管理的內容與這些文件實際對應的內容(在雲提供商的環境中)之間的映射。 請注意,在編寫 Terraform 配置文件時,您並不關心例如單個 EC2 實例的 ID 或將為您發布的安全組創建的 ARN。 然而,在內部,Terraform 需要知道給定的資源塊代表具有 ID/ARN 的具體資源。 這是檢測更改所必需的。 此外,狀態用於跟踪資源之間的依賴關係(這也是您通常不必考慮的事情!)。 它們用於構建可以(通常)並行化和執行的圖。 與往常一樣,我建議您閱讀有關 Terraform 狀態及其用途的優秀文檔。
由於狀態是您架構的唯一真實來源,因此您需要確保您和您的團隊始終在使用其最新版本,並且您不會因對狀態的不同步訪問而產生衝突。 相信我,您不想解決狀態文件上的合併衝突。
默認情況下,Terraform 將狀態存儲在磁盤上的文件中,該文件位於(每個 env 的)當前工作目錄中,作為terraform.tfstate
文件。 如果您知道自己將是工作中唯一的開發人員,或者只是在學習和試驗 Terraform,這沒關係。 從技術上講,您可以使其在團隊中工作,因為您可以將狀態提交到 VCS 存儲庫。 但是,您需要確保每個人都在處理最新版本的狀態,並且沒有人同時更改! 這通常是一個令人頭疼的問題,我強烈建議不要這樣做。 此外,如果有人加入您的單開發操作,您仍然需要為狀態配置替代位置。
幸運的是,這是一個內置於 Terraform 的良好解決方案的問題:所謂的遠程狀態。 要使遠程狀態正常工作,您需要使用可用的後端提供程序之一來配置後端。 以下後端示例將基於 AWS S3 和 AWS DynamoDB(AWS NoSQL 數據庫)。 你可以只使用 S3,但你會失去狀態鎖定和一致性檢查的機制(不推薦)。 如果您以前只使用本地狀態,配置遠程後端將為您提供第一次遷移狀態的選項,因此您不會丟失任何東西。 您可以在此處閱讀有關後端配置的更多信息。
不幸的是,存在先有雞還是先有蛋的問題:必須手動創建 S3 存儲桶和 DynamoDB 表。 Terraform 無法自動創建它們,因為還沒有狀態! 好吧,有一些像 https://github.com/gruntwork-io/terragrunt 這樣的解決方案可以使用 AWS CLI 自動完成,但我不想偏離這篇博文的主題。
有關 S3 和 DynamoDB 後端配置的重要信息包括:
- 在 S3 存儲桶上啟用版本控制以免受人為錯誤和墨菲定律的影響。
- DynamoDB 表對讀取和寫入有速率限制(稱為容量)。 如果您對遠程狀態進行了大量更改,請確保為該表啟用 DynamoDB AutoScaling 或配置足夠高的 R/W 限制。 否則,Terraform 將在執行大量調用時從 AWS API 收到 HTTP 400 錯誤。
綜上所述,可以將以下後端配置放在terraform.tf
中,以在 S3 和 DynamoDB 上配置遠程狀態。
terraform { # Sometimes you may want to require a certain version of Terraform required_version = ">= 0.11.7" # Stores remote state, required for distributed teams # Bucket & dynamoDB table have to be created manually if they do not exist # See: https://github.com/hashicorp/terraform/issues/12780 backend "s3" { bucket = "my-company-terraform-state" key = "app-name/stage" region = "eu-west-1" # 5/5 R/W Capacity might not be enough for heavy, burst work (resulting in 400s). Consider enabling Auto Scaling on the table. # See: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ProvisionedThroughput.html dynamodb_table = "terraform-state-lock-table" } }
一次要接受很多內容,但請記住,您為每個環境執行一次,然後就可以忘記它。 如果您需要對狀態鎖定進行更多控制,可以使用 HashiCorp Terraform Enterprise,但我不會在這裡介紹。
提供者
為了讓這個後端可以訪問並能夠與我們的雲提供商進行通信,我們需要配置所謂的provider 。 可以將以下塊放置在terraform.tf
文件中(對於每個 env):
provider "aws" { profile = "${var.profile}" region = "${var.region}" version = "~> 1.23.0" }
profile
和region
的變量存儲在terraform.tfvars
文件中,可以忽略。 profile
變量是指一個命名的配置文件,它使用標準憑證文件保存 AWS 雲的安全憑證。 請注意,我還設置了一些版本約束。 您不希望 Terraform 在您不了解每個後端初始化的情況下升級您的提供程序插件。 特別是考慮到 AWS 提供商的 2.x 版本需要仔細升級。
後端初始化
每個後端配置在開始時都需要一個初始化步驟,並且每次對其進行更改時。 初始化還會配置 Terraform 模塊(稍後會詳細介紹),因此當您添加這些模塊時,您還需要重新運行 init 步驟。 此操作可以安全運行多次。 請注意,在後端初始化期間,並非所有變量都可以被 Terraform 讀取以配置狀態,也不應該出於安全原因(例如,密鑰)。 為了克服這個問題,在我們的例子中,使用與默認配置不同的 AWS 配置文件,您可以使用-backend-config
選項並接受k=v
變量對。 這稱為部分配置。
terraform init -backend-config=profile=<aws_profile_name>
Terraform 文件共享一個受限於給定目錄的範圍。 這意味著子文件夾不直接連接到父目錄代碼。 然而,它定義了一個允許代碼重用、複雜性管理和共享的模塊。
工作流程
使用 Terraform 代碼時的一般工作流程如下:
- 為您的基礎架構編寫配置。
- 看看它會做出什麼實際的改變(
terraform plan
)。 - 或者,執行您在步驟 2 中看到的確切更改(
terraform apply
)。 - 轉到 1
地形規劃
Terraform plan
命令將向您顯示在發出apply
命令時將對您的基礎設施進行的更改列表。 多次發布plan
是安全的,因為它本身不會改變任何東西。
如何閱讀計劃
Terraform 中的對象(資源和數據源)可以通過其完全限定名稱輕鬆識別。
- 對於資源,ID 可能類似於:
<resource_type>.<resource_name>
— 例如,aws_ecs_service.this
。 - 對於模塊內的資源,我們有一個額外的模塊名稱:
module.<module_name>.<resource_type>.<resource_name>
——module.my_service_ecs_service_task.aws_ecs_service.this
。 - 數據源(模塊內部和外部):
(module.<module_name>).data.<resource_type>.<resource_name>
—module.my_service_ecs_service_task.data.aws_ecs_task_definition.this
。
資源類型特定於給定的提供者,通常包括其名稱 ( aws_…
)。 AWS 可用資源的完整列表可以在文檔中找到。
計劃將針對給定資源顯示五項操作:
-
[+]
添加 - 將創建一個新資源。 -
[-]
Destroy – 資源將被完全銷毀。 -
[~]
就地修改 - 將修改資源並更改一個或多個參數。 這通常是安全的。 Terraform 將向您顯示將修改哪些參數以及如何修改(如果可能)。 -
[- / +]
– 資源將被刪除,然後使用新參數重新創建。 如果對無法就地更改的參數進行了更改,則會發生這種情況。 Terraform 將向您顯示哪些更改會強制重新創建資源,並帶有以下紅色註釋:((forces new resource)
。 這是潛在的危險,因為有一段時間資源根本不存在。 它還可以破壞其他連接的依賴項。 我建議解決此類更改,除非您知道後果是什麼或不關心停機時間。 -
[<=]
– 將讀取數據源。 這是一個只讀操作。
以上是一個示例計劃。 我所做的更改是:
- 更改了第一個堡壘
instance_type
的 instance_type - 添加了第二個堡壘實例
- 更改了安全組的名稱
請注意,最後一個更改是自動檢測到父組名稱更改的安全組規則。 在我看來,整個計劃非常具有可讀性。 一些參數值顯示為<computed>
——這並不意味著它們會被改變,而是在這個階段它們不能被檢索和呈現(比如尚未創建的SG名稱)。 請記住,在plan
命令期間只會顯示更改的資源。 任何被省略的都不會被觸及。

通常,Terraform 命令接受附加參數。 計劃命令最重要的參數是-out
選項,它將您的計劃保存在磁盤上。 然後可以通過應用命令執行此保存的計劃(與保存的完全相同)。 這非常重要,因為否則可能會發生更改,例如,在您發布plan
和發布apply
之間的同事。 例子:
- 發布
plan
並驗證它看起來不錯。 - 有人在你不知情的情況下改變了狀態。
- 問題
apply
並且——哎呀,它做了一些計劃之外的事情!
值得慶幸的是,這個工作流程在 Terraform v0.11.0 中得到了改進。 從這個版本開始, apply
命令會自動為您提供一個您必須隨後批准的計劃(通過明確輸入yes
)。 優點是,如果您應用此計劃,它將完全按照呈現的方式執行。
另一個有用的選項是-destroy
,它將向您顯示將導致銷毀 Terraform 管理的所有資源的計劃。 您還可以使用-target
選項將特定資源作為銷毀目標。
地形應用
當我們應用給定的計劃時,Terraform 會退出並鎖定我們的狀態以確保排他性。 然後它繼續更改資源,最後推送更新的狀態。 需要注意的一件事是,某些資源比其他資源需要更長的時間才能完成。 例如,創建 AWS RDS 實例可能需要 12 分鐘以上,Terraform 將等待此完成。 顯然,Terraform 足夠聰明,不會因此阻止所有其他操作。 它創建請求更改的有向圖,如果沒有相互依賴關係,則使用並行性來加快執行速度。
導入資源
通常,Terraform 和其他“配置即代碼”解決方案會逐漸引入到已經存在的環境中。 過渡真的很容易。 基本上,任何未在 Terraform 中定義的東西都是不受管理和不變的。 Terraform 只關心它管理的內容。 當然,這可能會引入問題——例如,如果 Terraform 依賴於存在於其配置之外的某個端點,然後它會被手動銷毀。 Terraform 不知道這一點,因此無法在狀態更改期間重新創建此資源,這將導致計劃執行期間 API 出錯。 這不是我擔心的事情。 引入 Terraform 的好處遠大於壞處。
為了使引入過程更容易一些,Terraform 包含了import
命令。 它用於將已經存在的外部資源引入 Terraform 狀態並允許它管理這些資源。 不幸的是,至少在撰寫本文時,Terraform 無法為這些導入的模塊自動生成配置。 有此功能的計劃,但現在,您需要先在 Terraform 中編寫資源定義,然後將此資源導入以告訴 Terraform 開始管理它。 一旦導入狀態(以及在執行計劃期間),您將看到您在.tf
文件中編寫的內容與雲中實際存在的內容之間的所有差異。 這樣,您可以進一步調整配置。 理想情況下,不應該顯示任何更改,這意味著 Terraform 配置以 1:1 的比例反映了雲中已有的內容。
模塊
模塊是 Terraform 配置的重要組成部分,我建議您接受並經常使用它們。 它們為您提供了一種重用某些組件的方法。 一個例子是 AWS ECS 集群,它用於運行 Docker 容器。 要使這樣的集群正常工作,您需要配置許多單獨的資源:集群本身、管理單獨 EC2 實例的啟動配置、映像的容器存儲庫、自動縮放組和策略等。 您通常需要為單獨的環境和/或應用程序提供單獨的集群。
克服這個問題的一種方法是複制和粘貼配置,但這顯然是一種短視的解決方案。 做最簡單的更新會出錯。
模塊允許您將所有這些單獨的資源封裝在一個配置塊(稱為模塊)下。 模塊定義了輸入和輸出,它們是它與“外部世界”(或調用代碼)通信的接口。 此外,模塊可以嵌套在其他模塊中,允許您快速啟動整個獨立的環境。
本地和遠程模塊
與狀態類似,您可以擁有本地模塊或遠程模塊。 本地模塊與 Terraform 配置一起存儲(在單獨的目錄中,在每個環境之外但在同一個存儲庫中)。 如果您有一個簡單的架構並且不共享這些模塊,這沒關係。
然而,這有局限性。 很難對這些模塊進行版本控制並共享它們。 版本控制很重要,因為您可能希望在生產環境中使用 ECS 模塊的 v1.0.0,但想在暫存環境中試驗 v1.1.0。 如果模塊與您的代碼一起存儲,則對模塊代碼的每次更改都會反映在每個環境中(一旦運行apply
),這通常是不可取的。
對模塊進行版本控制的一種方便方法是將它們全部放在一個單獨的存儲庫中,例如 your-company/terraform-modules。 然後,在 Terraform 配置中引用這些模塊時,可以使用 VCS 鏈接作為源:
module "my-module" { source = "[email protected]:your-company/terraform-modules.git//modules/my-module?ref=v1.1.0" ... }
在這裡,我引用了 my-module 的 v1.1.0(特定路徑),我可以在不同環境中獨立於同一模塊的其他版本進行測試。
除此之外,還有模塊的可發現性和可共享性問題。 您應該努力編寫文檔齊全且可重用的模塊。 通常,不同的應用程序會有不同的 Terraform 配置,並且可能希望在它們之間共享相同的模塊。 如果不將它們提取到單獨的存儲庫中,這將非常困難。
使用模塊
通過定義一個特殊的模塊塊,可以在 Terraform 環境中輕鬆引用模塊。 以下是假設 ECS 模塊的此類塊的示例:
module "my_service_ecs_cluster" { source = "../modules/ecs_cluster" cluster_name = "my-ecs-service-${var.env}" repository_names = [ "my-ecs-service-${var.env}/api", "my-ecs-service-${var.env}/nginx", "my-ecs-service-${var.env}/docs", ] service_name = "my-ecs-service-${var.env}" ecs_instance_type = "t2.nano" min_size = "1" max_size = "1" use_autoscaling = false alb_target_group_arn = "${module.my_alb.target_group_arn}" subnets = "${local.my_private_subnets}" security_groups = "${aws_security_group.my_ecs.id}" key_name = "${var.key_name}" env_tag = "${var.env}" project_tag = "${var.project_tag}" application_tag = "${var.api_app_tag}" asg_tag = "${var.api_app_tag}-asg" }
傳遞的所有選項(除了一些全局選項,如source
)在此模塊的配置中定義為輸入(變量)。
模塊註冊表
最近 HashiCorp 推出了官方的 Terraform 模塊註冊表。 這是一個好消息,因為您現在可以從已經開發經過實戰測試的模塊的社區中汲取知識。 此外,其中一些具有“HashiCorp 已驗證模塊”徽章,這意味著它們經過審查和積極維護,給您額外的信心。
以前,您要么必須從頭開始編寫自己的模塊(並從錯誤中吸取教訓),要么使用發佈在 GitHub 和其他地方的模塊,而對它們的行為沒有任何保證(除了閱讀代碼!)
在環境之間共享數據
理想情況下,環境應該完全獨立,即使使用不同的 AWS 賬戶也是如此。 實際上,在某些情況下,一個 Terraform 環境可能會在另一個環境中使用某些信息。 如果您逐漸將架構轉換為使用 Terraform,則尤其如此。 One example might be that you have a global
env that provides certain resources to other envs.
Let's say env global
shares data with stage
. For this to work, you can define outputs at the main level of the environment like so:
output "vpc_id" { value = "${module.network.vpc_id}" }
Then, in the stage
environment, you define a datasource that points to the remote state of global
:
data "terraform_remote_state" "global" { backend = "s3" config { bucket = "my-app-terraform-state" key = "terraform/global" region = "${var.region}" dynamodb_table = "terraform-state-lock-table" profile = "${var.profile}" } }
Now, you can use this datasource as any other and access all the values that were defined in global
's outputs:
vpc_
Words of Caution
Terraform has a lot of pros. I use it daily in production environments and consider it stable enough for such work. Having said that, Terraform is still under active development. Thus, you will stumble on bugs and quirks.
Where to Report Issues and Monitor Changes
First of all, remember: Terraform has a separate core repo and repositories for each provider (eg, AWS). If you encounter issues, make sure to check both the core repo and the separate provider repositories for issues and/or opened pull requests with fixes. GitHub is really the best place to search for bugs and fixes as it is very active and welcoming.
This also means that provider plugins are versioned separately, so make sure you follow their changelogs as well as the core one. Most of the bugs I have encountered were resolved by upgrading the AWS provider which already had a fix.
Can't Cheat Your Way out of Cloud Knowledge
You cannot use Terraform to configure and manage infrastructure if you have no knowledge of how a given provider works. I would say this is a misconception and not a downside, since Terraform has been designed to augment and improve the workflow of configuration management and not to be some magic dust that you randomly sprinkle around and—poof! Environments grow! You still need a solid knowledge of a security model of each cloud, how to write, eg, AWS policies, what resources are available, and how they interact.
Prefer Separate Resources That Are Explicitly linked
There are certain resources—for example, the AWS security group or AWS route table—that allow you to configure the security rules and routes respectively, directly inside their own block. This is tempting, as it looks like less work but in fact will cause you trouble. The problems start when you are changing those rules on subsequent passes. The whole resource will be marked as being changed even if only one route/security rule is being introduced. It also gives implicit ordering to those rules and makes it harder to follow the changes. Thankfully, mixing those both approaches is not allowed now (see the note).
Best-practice example, with explicitly linked resources:
resource "aws_security_group" "my_sg" { name = "${var.app_tag}-my-sg" ... } resource "aws_security_group_rule" "rule_one" { security_group_ ... } resource "aws_security_group_rule" "rule_two" { security_group_ ... }
Terraform plan
Doesn't Always Detect Issues and Conflicts
I already mentioned this in the case where you were managing resources with Terraform that were relying on other, unmanaged infrastructure. But there are more trivial examples—for example, you will get an error if your EC2 instance has Termination Protection enabled, even though plan
would show you it's OK to destroy it. You can argue that this is what Termination Protection has been designed for, and I agree, but there are more examples of things you can do in theory/on plan but when executed will deadlock or error out. For example, you cannot remove a network interface if something is using it—you get a deadlock without an option to gracefully recover.
Syntax Quirks
There are also quirks related to how HCLv1 (the syntax language Terraform uses) has been designed. It has a couple of frustrating quirks. There is work underway to provide an improved version of the parser for HCLv2. The best way to read on the current limitations and the plan to overcome them is this fantastic blog series. In the meantime, there are workarounds for most of those issues. They are not pretty and they will fail once v0.12 comes out, but hey, it is what it is.
When State Update Fails
It sometimes happens that Terraform is not able to correctly push an updated state. This is usually due to underlying network issues. The solution is to retry the state update instead of running apply again, which will fork the state .
Another issue might happen when state lock (the synchronization primitive that prevents multiple users to update the same state) fails to be taken down by Terraform. This involves running terraform force-unlock
with the lock ID to take it down manually.
Thankfully, in case of such problems, Terraform provides you with a good description and steps you need to make to fix it.
Not Everything Is Fun to Manage Through Terraform
There are certain cases where Terraform is not my tool of choice. For example, configuring AWS CodePipeline and CodeBuild projects (AWS equivalent of CI/CD pipeline) is cumbersome when done through Terraform. You need to define each step through very verbose configuration blocks and things like “Login via GitHub” are a lot more complicated than using the UI. Of course, it's still possible if you prefer to have it codified. Well, I guess it's a good candidate for a well-written module!
Same thing goes for managing AWS API Gateway endpoints. In this case, using a dedicated serverless framework would be a better option.
When configuring AWS resources with Terraform, you will find yourself writing a lot of policies. Policies that would otherwise often be auto-generated for you (when using the UI). For those, I'd recommend the AWS Visual Editor and then copying the resulting policy JSON into Terraform.
結論
Using Terraform has been fun and I'll continue doing so. Initial steps can be a bumpy ride, but there are more and more resources that help to ease you in.
I'd definitely recommend taking Terraform for a spin and simply playing with it. Remember, though—be safe and test it out on a non-essential account. If you are eligible for AWS Free Tier, use it as a 12-month free trial. Just be aware it has limitations as to what you can provision. Otherwise, just make sure you spin the cheapest resources, like t3.nano instances.
I highly recommend extensions for Terraform support in various code editors. For Visual Studio Code, there is one with syntax highlighting, formatting, validation and linting support.
It's always valuable to learn new things and evaluate new tools. I found that Terraform helped me immensely in managing my infrastructure. I think working with Terraform will only get easier and more fun, especially once v0.12.0 ships with a major upgrade to the HCL syntax and solve most of the quirks. The traction and community around Terraform are active and vibrant. You can find a lot of great resources on things I didn't manage to cover in a single blogs post, eg, a detailed guide on how to write modules.