Sitemap

[Terraform] 基礎入門筆記

13 min readJun 6, 2021

Terraform 是一套近年來非常好用的 DevOps 管理工具,簡單來說,這套工具能透過易讀的程式碼,幫你管理多環境的系統,並保持環境一致性,以自動化的方式一鍵部署環境。

Terraform 有以下幾個特點:

  • 跨平台
    Terraform 能夠支援多種雲端環境或軟體,雲端平台像是 AWS、GCP、Azure 等都可利用 terraform 一鍵部署,更多 provider 可參考官方文件
  • 版本控制
    每一次執行部署前,Terraform 都會記錄這次與上次變更的差異並儲存起來,方便開發者檢視部署內容。
  • 自動化偵測雲端架構
    只要撰寫部署腳本,所有 infarstructure 都由 Terraform 自動建置完成,甚至可以匯入現有的雲端環境,使其加入至 Terraform 的管理之下,不需人工處理。
  • 環境一致性
    使用相同的配置腳本,可以確保部署在不同環境都有相同的環境配置。
  • HCL 語言
    Terraform 的配置是透過 HCL 語言撰寫,HCL 是一個由 HashiCorp 開發的語言,目的在於建構結構化的配置語言,可以搭配 command line 一起使用,同時兼容 JSON,特別適用於 DevOps 工具。

Write the Terraform Configuration

所有的 configuration、variable、output 都是寫在 .tf 的檔案中,也可根據不同功能分成不同 .tf,最後部署時會把目錄下所有的 .tf 整合在一起。

以下為部署 AWS EC2 的簡單範例:

provider "aws" {
access_key = "MY_ACCESS_KEY"
secret_key = "MY_SECRET_KEY"
region = "us-east-1"
}
resource "aws_instance" "my_instance" {
ami = "ami-830c94e3"
instance_type = "t2.micro"
}

→ provider

此區域決定要針對哪個平台做部署,其中會包括一些身分驗證的必要參數。

→ resource

此區塊是 provider 中提供的資源,以 AWS 為例,要部署的資源可以是 ec2 instance、s3 bucket、lambda function 等等,這些都會被抽象化成 terraform 的 resource。

基本的 template 可簡化成:

resource <resource_type> <resource_name> {
<identifier> = <expression>
}

根據 resource_type,哪些是必填的 identifier在官方文件上都寫得清清楚楚。

Deploy the Resource to the Cloud

配置文件寫好後,就可以利用 terraform command 將環境部署到 provider 上,以下有幾個在部署前需要知道的基本的指令:

  • terraform init
    Initialization 會根據 .tf中的 configuration,下載對應的 plugin 放到 .terraform 中。在初次建立專案時,需要先下 init 指令,將 provider binary 載入至本機。
  • terraform plan
    在部署前,可以先使用 plan 檢查本次與上次程式碼的更動(會有 +-的辨識符號),同時可偵測語法上的錯誤。
  • terraform apply
    有了 provider 的 plugin 後,apply 會將資源實際部署到 provider 上,完成後會產生對應的 .tfstate 檔案。
  • terraform fmt
    fmt 會自動幫你把程式碼對齊,對於排版很要求的開發者,這是一個好用的指令~
  • terraform destroy
    destroy 會根據 .tfstate 清除所有的資源。

What is .tfstate file?

terraform.tfstate這個檔案非常重要,它會在 apply完成後自動產生。 .tfstate 中包含所有 resource 的資訊,會記錄所有 Terraform 做過的事情,並依靠這個紀錄來進行 resource 的版控維護,執行 terraform 時都必須要有這個檔案。
因此建議可將此檔案備份至另一個地方(github、s3 bucket… etc),以防意外發生造成這個檔案的遺失。

了解以上 .tfproviderresource和基本指令後,你已經學會用 Terraform 部署最簡單的環境囉!

除了以上基本的資源定義寫法,Terraform 還有很多好用的 keyword:

→ variable

透過 variable 輸入自定義的變數,可以動態調整變數值。通常會習慣建立一個 variables.tf的檔案來統一管理變數(檔案名稱可自取)。

variable "ami_id" {
default = "ami-830c94e3"
type = string
}

在 resource configuration 中要引用此變數,則須加上前綴 var

resource "aws_instance" "my_instance" {
ami = var.ami_id
instance_type = "t2.micro"
}

其他傳入變數的方式

除了將變數寫死在 configuration 中,Terraform 也支援多種將變數傳入 .tf 檔中 variable block 的方法,以此來統一管理變數或增加安全性。

在使用以下方法前,都必須先定義 variable block:

# In variable.tf
variable "region" {}
variable "ami" {}
  1. .tfvars File
    - 可以透過將變數放在 .tfvars.tfvars.json 來定義變數值。
    - 在執行 plan / apply 時,terraform 會自動載入檔名為 terraform.tfvarsterraform.tfvar.json 的檔案。
# In terraform.tfvars
region = "us-east-1"
ami = "ami-830c94e3"

- 若要自定不同檔名的 .tfvars,需要使用 -var-file來傳入檔案。假設檔名叫做 vars.tfvars

terraform apply -var-file="vars.tfvars"

2. Command Line
執行 plan 或 apply 時,使用 -var 將變數傳入:

terraform apply -var "region=us-east-1" -var "ami_id=ami-830c94e3"

3. Environment Variable
Terraform 會自動抓取開頭為 TF_VAR 的環境變數傳入 variable 中

export TF_VAR_region="us-east"
export TF_VAR_ami="ami-830c94e3"
  • 變數加載的優先順序
    有這麼多傳入變數的方法,Terraform 載入變數時也有一定的順序,優先權從低到高為:
    1. Variable block in the configuration
    2. Environment Variable
    3. .tfvars
    4. Command Line -var or -var-file

變數型態

除了基本的 stringnumberbool,Terraform 也支援了多種集合類別結構類別的變數:

  • list
variable "account_id" {
default = ["123456789012", "98765432198"]
type = list
}
  • map
variable "region_ip" {
default = {
"us-east-1" = "192.168.0.0/16"
"us-west-1" = "192.168.1.0/16"
}
type = map
}

其他還有 setobjecttuple等型態,可參考 Type Constraints

→ module

module 中可以包含多個 resource,當架構越來越大時,如果所有的 resource 都集中在同一個 .tf,則不易維護或檢視。這時就可以使用 module 來分別管理這些 resource configuration。
此外,在 Terraform Registry 有很多已開發完成的完整 module,可直接載到本機使用。

以下面的 Lambda Module 為例:

module "my_lambda" {
source = "terraform-aws-modules/lambda/aws"
function_name = "my_lambda"
runtime = "python3.8"
}
  • source 是定義 module 所在的位置,可以為本地端自建的 module、 terraform registry、Github 等等,更多來源可參考 Module Sources
  • 由於這邊的範例是使用 registry 中的 lambda module,因此需要透過 terraform init 來下載遠端的資源。
  • 所有可用的 identerfir 和 output 參數,都能從 module registry 的文件上取得。

→ output

每個 resource 或 module 建立後都有屬於自己的 output value ,可以透過自定義的 output 來取得 resource 的重要資訊,在 apply 完成後 output value 會顯示在 terminal 上。

根據建立 service 的方式,會有不同的 output 寫法:

  • resource
output "instance_arn" {
value = aws_instance.my_instance.arn
}
# template
output "<resource_name>" {
value = <resource_type>.<resource_name>.<identerfir>
}
  • module
output "instance_arn" {
value = module.my_lambda.lambda_function_arn
}
# template
output "<module_name>" {
value = module.<module_name>.<identerfir>
}

指定 output 值並且apply 後,可查詢 output 值:

# all outputs
terraform output

# specific output
terraform output <output_name>

Resource Dependency

在 apply 的過程中,Terraform 可以自動判斷 resource 之間的相依性。

以下面為例,EC2 instance 要部署在自建的 VPC subnet 中:

resource "aws_instance" "my_instance" {
ami = "ami-04468e03c37242e1e"
instance_type = "t2.micro"
subnet_id = aws_subnet.my_subnet.id
}
resource "aws_vpc" "my_vpc" {
cidr_block = "192.168.0.0/16"
}
resource "aws_subnet" "my_subnet" {
vpc_id = aws_vpc.my_vpc.id
cidr_block = "192.168.0.0/24"
availability_zone = "us-east-1a"
}

在這個範例中,instance 中有使用 subnet,而 subnet 中有指定 vpc,因此透過 <resource-type>.<resource-name>.<identerfir> 這樣的方式指定來源,Terraform 能自動判斷創建 resource 的先後順序為:vpc → subnet → ec2

→ depends_on

然而,當 resource block 的參數中沒有指定其他 resource 的數據時,但仍然需要某些 resource 的相依性,Terraform 有提供 depends_on的語法,來指定明確的依賴項目。

以下面為例,在創建 EC2 instance 前,需要先建立 s3 bucket:

resource "aws_instance" "my_instance" {
ami = "ami-04468e03c37242e1e"
instance_type = "t2.micro"
subnet_id = aws_subnet.my_subnet.id

depends_on = [ aws_s3_bucket.my_bucket ]
}

resource "aws_vpc" "my_vpc" {
cidr_block = "192.168.0.0/16"
}

resource "aws_subnet" "my_subnet" {
vpc_id = aws_vpc.my_vpc.id
cidr_block = "192.168.0.0/24"
availability_zone = "us-east-1a"
}

resource "aws_s3_bucket" "my_bucket" {
bucket = "my_bucket"
acl = "public-read"
}

depends_on 這個關鍵字存在時,Terraform 必須先建立其指定的 resource,因此順序會變為:

  1. my_bucket
  2. my_vpc
  3. my_subnet
  4. my_instance

Build the Similar Resouce

有時部署的環境中,會需要建立複數個相同/相似的資源,使用 countfor_each 就不需要為每個 resource 單獨設置自己的 block。

  • count

以下範例為,創建 4 個相同的 instance,並且利用 count.index為每個 instance 取名:

resource "aws_instance" "my_instance" {
count = 4

ami = "ami-830c94e3"
instance_type = "t2.micro"

tags = {
Name = "Instance ${count.index}"
}
}
  • for_each

for_each 是透過 key/value 的方式,替不同條件指定不同的變數值。

以下範例為,在不同 region 建立不同的 ami instance:

resource "aws_instance" "my_instance" {
for_each = {
"us-east-1a" = "ami-830c94e3"
"us-east-1b" = "ami-n15w64e3"
}
availability_zone = each.key
ami = each.value
instance_type = "t2.micro"

tags = {
Name = "Instance ${each.key}"
}
}

--

--

Season Wang
Season Wang

Written by Season Wang

王璽禎 | The more tools you bring in the table, the more powerful you become.

No responses yet