第 1 章:使用 AWS 模組建立環境
想要透過 Terraform 創建一套包含 Web 層應用程式、App 層應用程式的基礎架構。
使用一個專用的 VPC,並遵循傳統的三層架構設計。 Web 層應用程式需要一個自動伸縮群組(AutoScaling Group)。他們的 App 層服務需要一個自動伸縮組,一個 S3 儲存桶以及一個資料庫。下面的架構圖描述了期望的結果:
)
在這個場景中,一個負責從零開始撰寫 Terraform 程式碼的團隊,負責編寫一組用於配置基礎架構及應用的模組。負責應用程式的團隊成員將使用這些模組來配置他們需要的基礎架構。
請注意,雖然此範例使用了AWS 命名,但所描述的模式適用於所有雲端平台。
經過對應用程式團隊的需求進行審核,模組團隊將此應用程式基礎架構分割成如下模組:網路、Web、App、資料庫、路由,以及安全性。
當 Terraform 模組團隊完成模組開發後,他們應該將模組匯入到私人模組註冊表中,並且向對應的團隊成員宣傳模組的使用方法。舉例來說,負責網路的團隊成員將會使用開發的網路模組來部署和配置相應的應用程式網路。
網路模組
網路模組負責網路基礎設施。它包含了網路存取控制清單(ACL)以及 NAT 閘道。它還可以包含應用程式所需的 VPC、子網路、對等連接以及 Direct Connect 等。
)
此模組包含這些資源是因為它們需要特定權限並且變化頻率較低。
- 只有應用程式團隊中有權建立或修改網路資源的成員可以使用該模組。
- 該模組的資源不會經常變更。將它們組合在單獨的模組中可以保護它們免於暴露在沒有必要的資料遺失的風險之中。
網路模組傳回一組其他工作區(Workspace)以及模組可以使用的輸出值。如果VPC 的創建過程是由多個方面組成的,我們可能最終會需要將該模組進一步切割成具有不同功能的不同模組。
應用程式模組
本場景中有兩個應用程式模組- 一個是 Web 層模組,另一個是 App 層模組。
Terraform 模組團隊完成這兩個模組的開發後,它們應分發給對應的團隊成員來部署他們的應用程式。隨著應用程式團隊的成員變得越來越熟悉 Terraform 程式碼,它們可以提出基礎架構方面的增強建議,或透過 Pull Request 配合他們自己的應用程式碼發布提交對基礎架構的變更請求。
Web 模組
Web 模組可建立和管理執行 Web 應用程式所需的基礎架構。它包含了 負載平衡器和自動伸縮群組,同時也可以包含應用程式中使用的 EC2 虛擬機器執行個體、S3 儲存桶、安全性群組,以及日誌系統。此模組接收一個透過 Packer 預先建置的包含最新 Web 層應用程式發布版本代碼的虛擬機器映像的 AMI ID 作為輸入。
)
此模組包含這些資源是因為它們是高度封裝的,並且它們變化頻率較高。
- 此模組中的資源高度內聚,並且與 Web 應用程式緊密相關(例如,此模組需要一個包含最新 Web 層應用程式程式碼版本的 AMI)。結果就是它們被編制進同一個模組,這樣 Web 應用團隊的成員就可以輕鬆地部署它們。
- 此模組的資源變更頻率較高(每次發布更新版本都需要更新對應基礎架構資源)。透過將它們組合在單獨的模組中,我們降低了將其他模組的資源暴露在沒有必要的資料遺失的風險中的可能性。
App 模組
App 模組建立和管理運行 App 層應用程式所需的基礎架構。它包含了負載平衡器和自動伸縮群組,同時也包含了應用程式中使用的 EC2 虛擬機器實例、S3 儲存桶、安全性群組,以及日誌系統。此模組接收一個透過 Packer 預先建置的包含最新 App 層應用程式發布版本代碼的虛擬機器映像的 AMI ID 作為輸入。
)
此模組包含這些資源是因為它們是高度封裝的,並且它們變化頻率較高。
- 此模組中的資源 高度內聚,並且與 App 應用程式緊密相關。結果就是它們被編制進同一個模組,這樣 App 層應用程式團隊的成員就可以輕鬆地部署它們。
- 此模組的資源變更頻率較高(每次發布更新版本都需要更新對應基礎架構資源)。透過將它們組合在單獨的模組中,我們降低了將其他模組的資源暴露在沒有必要的資料遺失的風險中的可能性。
資料庫模組
資料庫模組創建並管理了運行資料庫所需的基礎設施資源。它包含了應用程式所需的RDS 實例,也包含了所有關聯的儲存、備份以及日誌資源。
)
此模組包含這些資源是因為它們需要特定權限並且變化頻率較低。
- 只有應用程式團隊中有權建立或修改資料庫資源的成員可以使用該模組。
- 該模組的資源不會經常變更。將它們組合在單獨的模組中可以保護它們免於暴露在沒有必要的資料遺失的風險之中。
路由模組
路由模組建立並管理網路路由所需的基礎設施資源。它包含了公共託管區域(Hosted Zone)、Route 53 以及路由表,也可以包含私人託管區域。
)
此模組包含這些資源是因為它們需要特定權限並且變化 頻率較低。
- 只有應用程式團隊中有權建立或修改路由資源的成員可以使用該模組。
- 該模組的資源不會經常變更。將它們組合在單獨的模組中可以保護它們免於暴露在沒有必要的資料遺失的風險之中。
安全模組
安全模組可建立並管理所有安全所需的基礎設施資源。它包含一組IAM 資源,也可以包含安全群組(Security Group)及多因子認證(MFA)。
)
此模組包含這些資源是因為它們需要特定權限並且變化頻率較低。
- 只有應用程式團隊中有權創建或修改 IAM 或是安全資源的成員可以使用該模組。
- 該模組的資源不會經常變更。將它們組合在單獨的模組中可以保護它們免於暴露在沒有必要的資料遺失的風險之中。
建立模組的提示
除了範圍界定之外,我們在創建模組時還應牢記以下幾點:
嵌套模組
嵌套模組是指在目前模組中對另一個模組的引用。嵌套模組可以是外部的,也可以是目前工作空間內的。使用嵌套模組是一項強大的功能;然而我們必須謹慎實踐以避免引入錯誤。
對於所有類型的嵌套模組,請考慮以下事項:
- 嵌套模組可以加速開發速度,但可能會引發未知以及意料之外的結果。請在文件中清楚記錄輸入變數、模組行為以及輸出值。
- 通常來說,不要讓主模組的嵌套深度超過兩層。常用且簡單的工具模組,例如專門用來定義 Tag 的模組,則不受此限制制約。
- 嵌套模組必須包含必要的用來建立指定的資源配置的輸入參數以及輸出值。
- 輸入參數以及輸出值的命名應遵循一致的命名約定,以使得模組可以更容易被分享,以及將一個模組的的輸出值作為另一個模組的輸入參數。
- 嵌套模組可能會導致程式碼冗餘。必須同時在父模組與嵌套模組中聲明輸入參數和輸出值。
嵌套的外部模組
當我們需要使用那些定義了被多個應用程式堆疊、應用程式和團隊重複使用的標準化資源的通用模組時,嵌套的外部模組會很有用。外部模組通被集中管理和版本化控制,以使得消費者在使用新版本之前可以對其進行驗證。當我們依賴或希望使用位於外部的子模組時,請注意以下幾點:
- 外部模組必須獨立維護,並可供任何需要呼叫它的模組使用。使用模組註冊表可以確保這一點。
- 根據模組註冊要求,嵌套模組將擁有自己的版本控製程式碼倉庫,獨立於呼叫模組進行版本控制。
- 對嵌套模組的變更可能會影響呼叫模組,即使呼叫模組的呼叫程式碼及版本沒有變化,這會破壞呼叫程式碼的信任。
- 對呼叫模組如何使用外部模組在文件中進行記錄,使得模組行為以及呼叫 關係可以被輕鬆理解。
- 對外部模組的變更應該是向後相容的。如果向後相容是不可能的,則應清楚地記錄需要對任何呼叫模組進行的更改,並將之分發給所有模組使用者以避免意外。
嵌套的嵌入模組
在目前工作空間中嵌入一個模組使得我們能夠清楚地分離模組的邏輯元件,或是建立可在呼叫模組執行期間多次呼叫的可重複使用程式碼區塊。在下面的例子中,ec2-instance
是一個嵌入模組,根模組的 main.tf
引用了這個模組:
root-module-directory
├── README.md
├── main.tf
└── ec2-instances
└── main.tf
如果我們需要或傾向於使用嵌入模組,需要考慮以下幾點:
- 在「根模組」中加入嵌入模組意味著子模組與根模組被放在一起進行版本控制。
- 任何影響兩個模組間相容性的變更都會被快速發現,因為它們必須被一同測試和發布。
- (嵌入的)子模組不能被程式碼樹之外的其他模組調用,所以可能會增加重複的程式碼。舉例來說,如果嵌入的
ec2-instance
模組是用來創建一台被用在多個地方的標準化的計算實例,該模組無法以這種形式被分享。
標籤化模組名並記錄在文件中
為我們的模組創建並遵循一個命名約定將使得模組易於理解與使用。這將促進模組的採用和貢獻。以下是一個用來提升模組元素一致性的建議清單:
- 使用一個對人類來說一致且易於理解的模組命名約定。舉例來說:
terraform | cloud provider | function | full name |
---|---|---|---|
terraform | aws | consul cluster | terraform-aws-consul_cluser |
terraform | aws | security module | terraform-aws-security |
terraform | azure | database | terraform-azure-database |
- 使用人類可以理解的輸入變數命名約定。模組是編寫一次並多次使用的程式碼,因此請完整命名所有內容以提升可讀性,並在編寫程式碼時在文件中進行記錄。
- 對所有模組進行文檔記錄。確保文件中包含有:
- 必填的輸入變數:這些輸入變數應該是經過深思熟慮後的選擇。如果這些輸入變數值未定義,模組運作將會失敗。只在必要時為這些輸入變數設定預設值。例如
var.vpc_id
永遠不應該有預設值,因為每次使用模組時值都會不同。 - 可選的輸入變數:這些輸入變數應該有一個合理的,適用於大多數場景的預設值,同時又可以根據需求進行調整。公告輸入變數的預設值。例如
var.elb_idle_timeout
會有一個合理的預設值,但呼叫者也可以根據需求修改它的值。 - 輸出值:列出模組的所有輸出值,並將重要的輸出和資訊性的輸出包裝在對使用者友善的輸出範本中。
- 必填的輸入變數:這些輸入變數應該是經過深思熟慮後的選擇。如果這些輸入變數值未定義,模組運作將會失敗。只在必要時為這些輸入變數設定預設值。例如
定義並使用一個一致的模組結構
雖然模組結構是一個品味問題,我們應該將模組的結構記錄在文件中,並且在我們的所有模組之間保持統一的結構。為了要維持模組結構的一致:
- 定義一組模組必須包含的
.tf
文件,定義它們應包含哪些內容 - 為模組定義一個
.gitignore
(或類似作用的)文件 - 建立供樣例程式碼所使用的輸入變數值的標準方式(例如一個
terraform.tfvars.example
檔案) - 使用具有固定子目錄的一致的目錄結構,即使它們可能是空的
- 所有模組目錄都必須包含一個
README
文件詳細記述目錄存在的目的以及如何使用其中的文件