介紹

Terraform 是由 HashiCorp 開發的一種IaC管理工具,其誕生的原因來自現代 IT 基礎設施管理中對自動化、可重複性和跨平台支持的需求。隨著雲計算的普及,企業的基礎設施變得越來越動態,管理多雲環境(如 AWS、Azure、Google Cloud)和本地數據中心的資源變得複雜且容易出錯。傳統的手動配置方式不僅效率低,還容易導致環境之間的不一致。

Terraform 的產生主要為了解決以下需求:

  1. 基礎設施即代碼 (IaC):提供一種編程式方法來定義和管理基礎設施,使得資源配置版本化、可審核,並易於復現。
  2. 多雲支持:允許用戶通過單一工具管理不同雲平台和供應商的資源,避免過度依賴單一供應商。
  3. 可重複性和自動化:使用代碼編寫一次配置,無論是測試環境還是生產環境都可以一致地部署資源。
  4. 依賴管理:Terraform 能夠分析基礎設施資源之間的依賴關係,按正確順序創建或刪除資源,減少人為錯誤。

因此,Terraform 的出現使企業能夠更高效地管理和運營現代化的基礎設施,同時加強了敏捷性和穩定性。使用Terrafrom我們就不需要手動去建立或是刪除某個資源,只需要寫好腳本,就可以在不同的環境中部署相同的資源。

IaC 工具差異

講到IaC工具,不免也會聽到Ansible或是Chef等等,那這些工具與Terraform有什麼不同呢?

Ansible、Chef 和 Terraform 都是基礎設施即代碼(IaC)工具,但它們的設計目標和應用場景有很大的不同。
以下是 Terraform 與 Ansible 和 Chef 的主要差異:

工具類型和功能範圍

  • Terraform
    • 是一種宣告式的基礎設施編排工具,專注於資源的創建、更新和銷毀
    • 它的核心用途是管理基礎設施(如雲資源、網絡設置等),用戶定義目標狀態。
    • 例子:適用於創建和管理基礎設施資源(如虛擬機、網絡設置、存儲桶等),並在多雲環境中實現統一的基礎設施管理。
  • AnsibleChef
    • 主要是配置管理工具,用於安裝軟體、管理系統配置以及持續性地維護應用程序的狀態
    • 它們的作用在於配置已經存在的資源,而不是創建資源。
    • 例子:更適合於配置操作系統、部署應用程序、安裝和維護軟體(如設置服務器的防火牆規則或安裝特定版本的軟件包)。

宣告式 vs 指令式

  • Terraform
    • 是宣告式的,用戶只需定義基礎設施的最終狀態,具體如何執行由 Terraform 處理。
  • AnsibleChef
    • 通常是指令式的,用戶需要明確定義執行的步驟
    • 例如「先安裝 Nginx,然後修改配置檔,再啟動服務」。
    • 這對於複雜的操作流程更為靈活,但需要更多的細節管理。

Terraform 流程

指令主要有以下幾個:

  • terraform init:初始化工作目錄,下載 provider 插件。
  • terraform validate:驗證配置文件的語法和邏輯錯誤。
  • terraform fmt:格式化配置文件,使其符合 Terraform 的標準格式,這個指令會幫你重新排版。
  • terraform plan:生成計劃,可以預覽將要執行的操作。但是不會真正創建或更新基礎設施。
  • terraform apply:應用計劃,創建或更新基礎設施。
  • terraform destroy:刪除基礎設施,通常用於測試環境的清理。

其他參數設定

  • -auto-approve
    • 可以自動跳過這個確認步驟,直接應用計畫,適合自動化腳本或 CI/CD 流程。
    • 當你執行 terraform apply 時,Terraform 會預設要求用戶手動確認 (yes) 才會執行計畫 (plan) 中的變更。
  • plain.out
    • 你可以先使用 terraform plan -out=plan.out 生成計畫文件,該文件詳細記錄了 Terraform 將執行的操作(例如資源新增、刪除或修改)。
    • 在 terraform apply 時指定該文件(如 terraform apply plan.out),可保證應用的計畫和生成時一致,避免非預期的更改。
    • 簡單來說,我們會建議terraform plan -out plan.out,然後再terraform apply -auto-approve plan.out這樣可以確保 apply 的結果是根據 plan 來的

我們可以設定alias在 ~/.bashrc,這樣就可以直接使用簡寫指令。

1
2
3
4
5
6
7
# 設定alias在 vim ~/.bashrc
alias tf="terraform"
alias tfv="terraform validate"
alias tfp="terraform plan -out plan.out"
alias tfdp="terraform apply -auto-approve plan.out"
alias tfd="terraform apply -auto-approve"
alias tfr="terraform destroy -auto-approve"

核心元件


以下是對圖片中每個 Terraform 元件的簡單介紹與用途說明:

基礎篇

1. Providers (供應商)

  • 用途
    • 負責與特定的雲端平台(如 AWS、Azure、Google Cloud)或服務(如 Kubernetes)互動。它提供 API 的橋樑,允許 Terraform 管理這些資源。
    • 可以指定 provider 版本(最佳實作),以確保 Terraform 與特定版本的 provider 兼容。
  • 範例:AWS Provider 幫助創建 EC2、S3 等資源。
1
2
3
4
5
6
7
8
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.40.0"
}
}
}

2. Resource (資源)

  • 用途
    • 描述要管理的基礎設施資源,是 Terraform 的核心組件。例如 EC2 實例、S3 存儲桶。
  • 範例
1
2
3
4
resource "aws_instance" "example" {
ami = "ami-12345678"
instance_type = "t2.micro"
}

3. Provisioners (配置器)

  • 用途
    • 必須放在Resource裡面
    • 用於在資源建立後執行額外的配置步驟(如執行腳本、安裝軟體)。
    • 應該謹慎使用,因為它們違反了聲明式的理念。
  • 範例:在 EC2 上執行初始化腳本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

resource "google_compute_instance" "example" {
name = "example-instance"
machine_type = "e2-micro"
zone = "asia-east1-b"

boot_disk {
initialize_params {
image = "debian-cloud/debian-10"
}
}

network_interface {
network = "default"

access_config {
// Ephemeral IP
}
}

##################################################################################
# PROVISIONER
##################################################################################
# 成功案例,執行電腦本機路徑
provisioner "local-exec" {
command = "echo ${google_compute_instance.example.network_interface[0].network_ip} > ./ip_address_local_exec.txt"
}

# # 傳送到虛擬電腦本機(這台電腦要先能通過去並且ssh)
# provisioner "file" {
# content = google_compute_instance.example.network_interface[0].network_ip
# destination = "/tmp/ip_address_file.txt"
# }

# # 無法連線到遠端(這台電腦要先能通過去並且ssh)
# provisioner "remote-exec" {
# inline = [
# "echo ${google_compute_instance.example.network_interface[0].network_ip} > /tmp/ip_address_remote_exec.txt"
# ]
# }
}

4. Variables (變數)

  • 用途
    • 用於設置 Terraform 模組的參數,變數可以用來傳遞值到模組中,像是指定 GCP 專案 ID 或 AWS 密鑰。
    • 這種方式稱為依賴注入可以使用變數來指定開發環境、生產環境等不同的配置。
    • 可以定義不同類型的變數,例如字符串、數字、布林值等。
  • 範例:設定 GCP 專案 ID 和 GCS 存儲桶名稱:

variables.tf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 我們主要有三個變數名稱:GCP_PROJECT、gcs_name、location
variable "GCP_PROJECT" {
type = string
description = "GCP project ID"
default = "terraform101-442105"
}

variable "gcs_name" {
type = string
default = "quick-start-gcs-bucket-variable"
}

variable "location" {
type = string
default = "asia-east1"
}

main.tf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 就可以直接使用 var.<variable-name> 來取得值
terraform {
required_version = ">=1.0"

required_providers {
google = {
source = "hashicorp/google"
version = ">=4.40.0"
}
}
}

provider "google" {
project = var.GCP_PROJECT
}

resource "google_storage_bucket" "quick_start_gcs" {
name = var.gcs_name
location = var.location
force_destroy = true
}

可以使用console 測試變數

1
2
$ tf console 
$ var.location # 會印出 asia-east1

變數型態

子串 寫法 範例
字串 type=string us-west-1
數值 type=number 1
布林 type=bool true, false
列表(型態) type=list(string) [“a”, “b”, “c”]
地圖(型態) type=map(string) { key = “value” }

變數繼承順序

變數可以在多個地方定義,並且有一定的繼承順序,如下所示:

  1. 命令行參數(如 -var 'foo=bar'):優先權最高,可以覆蓋所有其他值。
  2. Terraform 環境變數
    • Terraform 可以直接讀取環境變數,尤其是與提供商相關的認證和配置。環境變數通常是以 TF_VAR_ 為前綴的,後面跟變數名稱。
    • export TF_VAR_region="asia-northeast1" 輸入後,就可以在 Terraform 中使用 var.region 來取得值。或是直接使用variable "region" {}自動帶入。
  3. Terraform 變數文件
    • terraform.tfvars 中直接設置 key=value 的方式,可以直接讀取。例如: region = "asia-northeast1"
    • 使用的方式可以在variable.tf檔案設置如下:variable "region" { type=string } 就不用指定default了,儘管有指定也會被覆蓋掉。
  4. Terraform 設定文件
    • 基本上就是variable.tf裡面我們會設定variable "region" { type=string default="asia-northeast1" },這樣就可以直接使用了。

5. Data Source (資料來源)

  • 用途
    • 當需要使用現有基礎設施,像是現有的PVC, 子網, instance等,可以使用data source來取得資訊並引用。
    • 從以下範例為例,我們使用google_client_config這個 data soucr 檢索當前Google Cloud 客戶端配置的相關資訊,可以取得目前正在使用的專案ID、區域、用戶帳戶等。
  • 範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用 data source 取得 Google Cloud 客戶端配置 預設服務帳戶
data "google_client_config" "default" {}
data "google_compute_default_service_account" "default" {} # 用於檢索與當前 Google Cloud 項目相關聯的默認服務帳戶(service account)

# 使用 data source 取得專案 ID 和區域
output "project_id" {
value = data.google_client_config.default.project
}
output "region" {
value = data.google_client_config.default.region
}

# 使用 data source 取得預設服務帳戶
module "gke" {
...
service_account = data.google_compute_default_service_account.default.email
...
}

如果想要取得bucket的相關data souce則要確保name要是存在的,否則會報錯。

1
2
3
4
5
6
7
8
resource "google_storage_bucket" "bucket" {
name = "my-new-bucket" # 建立一個 bucket 名稱為 my-new-bucket
location = "US"
force_destroy = true
}

data "google_storage_bucket" "my-new-bucket" { # 取得 bucket 名稱為 my-new-bucket 的資訊
}

6. Outputs (輸出)

  • 用途:定義從 Terraform 配置中輸出的值,方便其他模組或用戶查看重要信息。
  • 範例:取得 data source 的內容輸出 GCP 專案 ID 和區域:
1
2
3
4
5
6
7
8
9
10
11
# 使用 data source 取得 Google Cloud 客戶端配置 預設服務帳戶
data "google_client_config" "default" {}
data "google_compute_default_service_account" "default" {} # 用於檢索與當前 Google Cloud 項目相關聯的默認服務帳戶(service account)

# 使用 data source 取得專案 ID 和區域
output "project_id" {
value = data.google_client_config.default.project
}
output "region" {
value = data.google_client_config.default.region
}

7. State (狀態)

  • 用途
    • Terraform 用於追蹤已管理的資源的當前狀態,並比較JSON配置文件的期望狀態,決定資源的增刪改操作。
  • 管理State的最佳實踐
    1. 不要手動修改 tfstate 文件: tfstate 是 Terraform 管理的核心數據,手動修改可能導致狀態不一致或損壞。
    2. 使用遠端後端存儲 (Backend): 在團隊協作或生產環境中,將狀態文件存儲到安全的遠端後端(如 S3)是必要的。
    3. 啟用加密:如果使用 S3、GCS 等後端,啟用加密來保護狀態文件的敏感數據。
    4. 啟用版本控制:遠端後端通常支援版本控制(如 S3 的版本控制功能),可以用於恢復意外損壞的狀態文件。
    5. 鎖定狀態文件:使用遠端後端(如 S3 搭配 DynamoDB 或 Terraform Cloud)進行狀態鎖定,防止多用戶同時修改狀態文件。
    6. 敏感數據管理:tfstate 文件可能包含敏感信息(如密碼、API 密鑰),需要妥善保護,避免公開或未加密存儲。
  • 範例

8. Backend (遠端狀態後台)

  • 用途
    • 定義 Terraform 的狀態檔案tfstate應存儲於哪裡,支持多用戶協作並發操作。
    • 可以把terraform.tfstate的狀態儲存在S3, GCP等等,這樣不僅僅可以做版控,還可以協同編輯等等。
  • 範例
    1. 通常在專案裡面可以針對backend建立資料夾/state,然後在裡面建立backend.tf檔案如下:
    1
    2
    3
    4
    5
    resource "google_storage_bucket" "quick_start_gcs" {
    name = "gcs-bucket-state"
    location = "asia-east1"
    force_destroy = true
    }
    1. 之後建立terraform的時候就可以指定backend如下:
    1
    2
    3
    4
    5
    6
    terraform {
    backend "gcs" {
    bucket = "gcs-bucket-state" # <=== 這邊要對應到上面的名稱
    prefix = "terraform/state"
    }
    }

進階篇

上述都是比較基礎的元件與設定教學,接下來如果專案比較大,可能會需要進一步瞭解以下元件。

  • Modules (模組):模組是可重複使用的配置單位,包含資源、變數等定義,幫助組織代碼並實現代碼重用
  • Dependency (依賴):Terraform 會自動解析資源之間的依賴關係,並按正確順序創建或刪除資源
  • Lifecycle (生命週期):控制資源的創建、更新和刪除行為,如何處理資源的變更。
  • Workspaces (工作區):用於管理不同環境(如開發、測試、生產)的狀態和配置,避免混淆和衝突。

1. Modules (模組)

  • 用途

    • 模組是可重複使用的配置單位,包含資源、變數等定義,幫助組織代碼並實現代碼重用。
    • 通常會建立一個資料夾稱作./modules 在裡面建立相關的檔案。
  • 範例

./modules/main.tf 跟一般設定沒兩樣,設定 resource

1
2
3
4
5
resource "google_storage_bucket" "shannon_gcs" {
name = var.gcs_name
location = var.location
force_destroy = true
}

./modules/variable.tf 跟一般設定沒兩樣,設定 variable

1
2
3
4
5
6
7
8
9
variable "gcs_name" { # 可以透過外部module引入變數
type = string
default = "quick-start-gcs-bucket-state"
}

variable "location" { # 可以透過外部module引入變數
type = string
default = "asia-east1"
}

./modules/output.ts 注意的是儘管使用 apply 也不會印出,除非在main.tf設定output

1
2
3
4
5
6
7
output "bucket_name" {
value = google_storage_bucket.shannon_gcs.name
}

output "bucket_location" {
value = google_storage_bucket.shannon_gcs.location
}

main.tf 真正執行terraform apply的檔案

1
2
3
4
5
6
7
8
9
10
11
# 引用 module 
module "shannon-module-gcs" { # 可以自己取名
source = "./modules/test" # 引用的地方
gcs_name = "shannon-gcs-module-test" # 會放到Module裡面的變數
location = "asia-east1"# 會放到Module裡面的變數
}

# ./path/output.tf 需要設定output.tf指向module的output才會印出東西
output "bucket1_name" {
value = module.shannon-module-gcs.bucket_name # 會去抓module.output.<name>
}

2. Dependency (依賴)

  • 用途
    • 模組之間可以相互依賴,也可以獨立存在,也因死兩個模組之間可能產生存在先後的關係
    • 模組之間的依賴關係可以透過 resource 或 module 來建立。
    • Terraform 會自動計算依賴關係,確定模組的創建以及銷毀的順序。
  • 範例
    • 可以透過 depends_on 參數來強制指定模組之間的依賴關係。
    • 使用 depends_on 參數時,需特別注意相互依賴可能會產生的循環依賴問題(產生雞生蛋、蛋生雞問題)。

./main.tf
可以使用 depends_ondepend-1 先建立,再建立 depend-2,在銷毀的時候則是先 depend-2depend-1

1
2
3
4
5
6
7
8
9
10
11
12
module "shannon-module-depend-1" {
source = "./modules/gcs"
gcs_name = "shannon-gcs-1"
location = "asia-east1"
}

module "shannon-module-depend-2" {
source = "./modules/gcs"
gcs_name = "shannon-gcs-2"
location = "asia-east1"
depends_on = [module.shannon-module-depend-1]
}

3. Lifecycle (生命週期)

  • 用途
    • Terraform Lifecycle 指的是指令碼的執行順序和執行方式,主要包含了以下三個階段: createupdatedestroy
    • 使用者可以透過指定自定義的執行順序來控制 Lifecycle 的執行方式,通常是用來更好的控制資源的創建與更新策略。
  • 範例
    • 常見Lifecycle屬性:
      避免被刪除:prevent_destroy ⇒ 可以避免發生刪除
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      resource "google_storage_bucket" "shannon-lifecycle-gcs" {
      name = "shannon-lifecycle-test"
      location = var.location
      force_destroy = true

      labels = {
      lifecycle : "original2"
      }

      # lifecycle 如果把labels添加在ignore_change的話labels儘管有更新有不會發生更新
      lifecycle {
      prevent_destroy = true
      }
      }
      忽略改變:ignore_changes ⇒ 例如當label有更新時,我們可以忽略更新
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      resource "google_storage_bucket" "shannon-lifecycle-gcs" {
      name = "shannon-lifecycle-test"
      location = var.location
      force_destroy = true

      labels = {
      lifecycle : "original2"
      }

      # lifecycle 如果把labels添加在ignore_change的話labels儘管有更新有不會發生更新
      lifecycle {
      ignore_changes = [ labels ]
      }
      }

4. Workspaces (工作區)

  • 概念
    • 可視為將不同環境的相關設定集合在一起的容器,例如開發、測試、生產等。
    • Terraform 可以透過 workspace 切換不同的環境,讓使用者在同一個 Terraform 設定檔中管理多個環境
  • 注意事項
    • 不同 Workspace 之間的資源是獨立的,不會互相干擾
    • 在不同 Workspace 中使用相同的變數時,需要在每個 Workspace 中單獨設置。
  • 最佳實踐
    • 我們擔心使用相同的部署檔在不同的workspace很有可能會因為在別的資源產生相同名稱的資源,導致切換成別的workspace時產生資源發生衝突
    • 我們可以透過${terraform.workspace}來根據當前的space來使用
    • 但是最佳實踐還是每個專案都有自己的設定檔比較好
  • 範例

Demo 步驟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 創建dev/prd兩個工作空間workspace
terraform workspace new dev
terraform workspace new prd
# 查看有哪些workspace
terraform workspace list

# 選擇workspace dev 工作空間做初始化,觀察terraform.tfstate.d資料夾
terraform workspace select dev
terraform init
terraform plan

# 回到prd的工作空間workspace做初始化,觀察terraform.tfstate.d資料夾
terraform workspace select prd
terraform init
terraform plan

# 做部署
terraform apply -auto-approve

# 清理dev/prd的資源
terraform workspace select dev
terraform destroy -auto-approve

terraform workspace select prd
terraform destroy -auto-approve

最佳實踐參考

  1. 可以遵循 Terraform 最佳實踐來建立專案,或是參考qwedsazxc78/terraform-project-best-practice
  2. 基礎架構有很強的依賴性,最好使用數字標號,告知他們建立的順序

總結

在了解元件之後,元件與元件之間的關係我覺得可以餐這以下這個圖片這樣說明: