[Terraform] 基礎入門筆記
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),以防意外發生造成這個檔案的遺失。
了解以上
.tf
、provider
、resource
和基本指令後,你已經學會用 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" {}
.tfvars
File
- 可以透過將變數放在.tfvars
或.tfvars.json
來定義變數值。
- 在執行 plan / apply 時,terraform 會自動載入檔名為terraform.tfvars
或terraform.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
◎ 變數型態
除了基本的 string
、number
、bool
,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
}
其他還有 set
、object
、tuple
等型態,可參考 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,因此順序會變為:
- my_bucket
- my_vpc
- my_subnet
- my_instance
Build the Similar Resouce
有時部署的環境中,會需要建立複數個相同/相似的資源,使用 count
和 for_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}"
}
}