Terraform State 之你的 Local State 不是我的 State
State 是初學 Terraform 的核心概念,本章節會講解基本的 State 原理。
課程內容與代碼會放在 Github 上: https://github.com/chechiachang/terraform-30-days
賽後文章會整理放到個人的部落格上 http://chechia.net/
上一講 Day 02-是在 Hello?什麼都要 Hello 一下之 Hello Terraform,我們操作 terraform 指令,來 create / update / destroy 遠端的資源。在執行完成 terraform apply 後,本地資料夾會產生一個 terraform.tfstate 檔案。
首先,我們看一下 terraform.tfstate 檔案的內容。你可以使用文字編輯器,或是透過 shell 與 jq 工具來檢視 State
cat terraform.tfstate
cat terraform.tfstate | jq keys
[
"lineage",
"outputs",
"resources",
"serial",
"terraform_version",
"version"
]
其中 .resources 是紀錄 .tf 檔案產生的資源,在遠端的 instance 的實際資料。換句話說,Terraform apply 後產生資源滿足 .tf 的描述,而實際在遠端的實體是哪一個,有哪些資料,紀錄在 State 中。
再舉個例,如果我們想要產生多個不同的 _poc/foundation/*.tf
中的 resource,複製 .tf 檔案在 apply,會獲得另一組 foundation resource,如另一組 resource group 與 storage account,有相同的參數,但有不同的遠端 id。
我們還可以比較 .tfstate 的內容,與 azure console 上看到的內容,更能理解兩者個關係。
根據官方文件 描述 State 的設計與功能,這邊簡述幾個重點,底下會有範例詳述
釐清名詞 State
使用 Local State 有許多好處
然而使用 Local State 也有以下幾個問題
Q: 沒有 .tfstate 檔案就無法使用 Terraform 嗎?可是我的 .tf 檔案裡 name 寫得很清楚,這樣 Terraform 抓不到遠端相同名字的資源嗎?
我們可以做個實驗,切換到另外一個 foundation cloned 資料夾,內容與 foundation 完全一致。
cd ../foundation_cloned
terraform init && terraform plan
plan 的結果是什麼?
是否令人有些疑惑?
我們原先預想透過相同的 .tf 檔案來管理 resourceGroup/terraform-30-days-poc,然而 Terraform 無法追蹤遠端已經存在的 resource group,而是選擇 create 一個新的 resource group,provider API 送出後,遠端的 Azure 回傳錯誤
更明確的說明:沒有 State 便無法 mapping .tf resource,與遠端 resource
我們可以進一步檢視,.tfstate 檔案的哪些內容,使得一邊可以正常使用 Terraform,一邊何謂產生錯誤。使用 jq 工具來檢視 State 中與 resource gropu 有關的資料。
cat terraform.tfstate | jq '.resources[0]'
{
"mode": "managed",
"type": "azurerm_resource_group",
"name": "rg",
"provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "/subscriptions/6fce7237-7e8e-4053-8e7d-ecf8a7c392ce/resourceGroups/terraform-30-days-poc",
"location": "southeastasia",
"name": "terraform-30-days-poc",
"tags": null,
"timeouts": null
},
"sensitive_attributes": [],
"private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo1NDAwMDAwMDAwMDAwLCJkZWxldGUiOjU0MDAwMDAwMDAwMDAsInJlYWQiOjMwMDAwMDAwMDAwMCwidXBkYXRlIjo1NDAwMDAwMDAwMDAwfX0="
}
]
}
cat terraform.tfstate | jq '.resources[0].instances[0].attributes'
{
"id": "/subscriptions/6fce7237-7e8e-4053-8e7d-ecf8a7c392ce/resourceGroups/terraform-30-days-poc",
"location": "southeastasia",
"name": "terraform-30-days-poc",
"tags": {},
"timeouts": null
}
Terraform 產生 resource group 後,將 azure API 回覆的 resources 各項參數紀錄在 .tfstate 中。下次要再進行編輯時,我們編輯 .tf 檔案,Terraform 則會依據 .tfstate 檔案去 mapping 遠端的 resource group,進行 plan 與 apply。
熟悉 RESTful API 的朋友可以這樣想:使用 POST API 產生物件時,後端 server 會回傳 id 在 response body 中,下次要編輯這個相同物件,則需要使用 id 作為辨識。Terraform 其實是相同的原理,只是在這個例子中,Terraform 協助託管了 id 這個 metadata。
既然使用 terraform 時,必須仰賴 State .tf 檔案,那是否一起協作的團隊成員就必須要取得 .tfstate 檔案,才能正確的操作 Terraform?
是的,這也是使用 Local Backend 的 .tfstate 檔案,最大的問題,會造成多人協作十分困難。過往舊版的 Terraform 有幾個妥協的做法:
將 .tfstate 檔案與 .tf 檔案一起納入版本控制系統,具體流程可能是這樣
理想上是這樣,但實務上還是非常不便
本課程不建議把 .tfstate 加入到版本控制,本 repository 已經將.tfstate 加入到 .gitignore 中。
所有在 terraform plan / apply 中產生的 data 與 meta-data,都會紀錄在 tfstate 中,那是否有一些 State 內容是敏感資料,不希望讓他人看到?
我們可以使用 _poc/user/
為例子。首先檢視一下內容 .tf 檔案,這個檔案使用 Terraform random password 來產生一組隨機密碼,然後使用這組密碼作為新的 User 的登入密碼。
# _poc/user/ad_user.tf
resource "random_password" "terraform" {
length = 16
special = true
override_special = "_%@"
}
resource "azuread_user" "terraform" {
user_principal_name = "terraform@chechia.net" # Need valified domain on Azure AD
display_name = "Terraform Runner"
mail_nickname = "terraform"
password = random_password.terraform.result
}
產生 user terraform 資源
terraform apply
Plan: 2 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ password = (sensitive value)
+ user_principal_name = "terraform@chechia.net"
...
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
password = <sensitive>
user_principal_name = "terraform@chechia.net"
Apply 完成後,我們可以看一下 _poc/user
產生的資料,這邊有點有趣
我們可以在用其他方法印出 sensitive 資料:
terraform output
password = <sensitive>
user_principal_name = "terraform@chechia.net"
terraform output -json
{
"password": {
"sensitive": true,
"type": "string",
"value": "QfgdxMwVRKr41nHG"
},
"user_principal_name": {
"sensitive": false,
"type": "string",
"value": "terraform@chechia.net"
}
}
cat terraform.tfstate | jq '.resources[1].instances[0]'
{
"schema_version": 0,
"attributes": {
"id": "none",
"keepers": null,
"length": 16,
"lower": true,
"min_lower": 0,
"min_numeric": 0,
"min_special": 0,
"min_upper": 0,
"number": true,
"override_special": "_%@",
"result": "QfgdxMwVRKr41nHG",
"special": true,
"upper": true
},
"sensitive_attributes": [],
"private": "bnVsbA=="
}
我們可以看到,所有密碼還是明碼的印出來。
更好的方式是
下堂,我們要來使用遠端的 Backend 與 State
嘗試使用 terraform state 的幾個指令
terraform state --help
terraform state list
terraform state show
terraform destroy 刪除所有本課程的產物
閱讀以下官方文件