Day 13 講到 Terraform 會把 resource 狀態 state 存在 backend 裡,到目前為止我們的 backend 都是 local 檔案:
terraform.tfstate
:記錄 remote resource state 的檔案
terraform.tfstate.backup
:上述檔案的備份檔,有時候非正常中斷 apply 操作會導致 state file 整個被清空,可以用備份檔救回來
看過 terraform.tfstate
檔案內容的朋友應該知道它就是個文字檔、記錄著所有 terraform 管理的 resource 的資訊,其中包含 credential,例如 IAM user 的 access key id 及 secret。沒錯,它是明碼。所以前面有說 state 檔案不建議 commit 進 git repository,不然就所有 credential 永存 git history 了……安捏母湯~
另外就算 state 檔案裡沒有 credential,以多人協作來說,也不適合使用 local 檔案存放 state。跟程式碼一樣,多人共同改同一個檔案可能會造成 conflict,state 的 conflict 跟 code 不一樣,它會對應 cloud 上真實的 resource,這 conflict 很難解啊~
基於上述原因,我們要改用 remote 且對 state 有所保護的 backend,Gitlab 有提供管理 terraform state 的功能,我們把 state 轉移到 Gitlab 上吧~
Gitlab Terraform State 透過 Terraform 的 http backend 來保存 state file。Gitlab 裡的 terraform state 有 version,我們可以用 api 取得先前版本的 state。重要的是 Gitlab 會加密 state 檔案後才保存,透過 Gitlab API 存取 state 時會自動加解密。
backend 設定是放在 terraform block:
terraform {
backend "remote" {
organization = "example_corp"
workspaces {
name = "my-app-prod"
}
}
}
每份 terraform configuration 只能有一個 backend block。修改過 backend block 後,在執行 plan
跟 apply
等等指令前,要先執行 terraform init
重新設定好 backend。可以使用的 backend 類型可以參考 官方文件。
雖然 terraform 本來就有備份 state,但操作前還是多備份 terraform.tfstate
一下~
首先在 Gitlab 的 Settings > General 找到 Project name 跟 Project ID。接著在 main.tf
定義 backend block,使用 http
backend:
terraform {
backend "http" {
}
}
到 Gitlab 產生個 personal access token,權限要有 api
scope :
下圖隱藏起來的字串就是 token(這頁只會顯示一次):
接下來,執行 terraform init
進行 migrate state:
$ terraform init \
-backend-config="address=https://gitlab.com/api/v4/projects/<YOUR-PROJECT-ID>/terraform/state/<YOUR-STATE-NAME>" \
-backend-config="lock_address=https://gitlab.com/api/v4/projects/<YOUR-PROJECT-ID>/terraform/state/<YOUR-STATE-NAME>/lock" \
-backend-config="unlock_address=https://gitlab.com/api/v4/projects/<YOUR-PROJECT-ID>/terraform/state/<YOUR-STATE-NAME>/lock" \
-backend-config="username=<YOUR-USERNAME>" \
-backend-config="password=<YOUR-ACCESS-TOKEN>" \
-backend-config="lock_method=POST" \
-backend-config="unlock_method=DELETE" \
-backend-config="retry_wait_min=5" \
-migrate-state
其中 <YOUR-PROJECT-ID>
要改用你的 Gitlab project ID,<YOUR-STATE-NAME>
可以指定 state 的名稱,可以用環境當作名稱,像是 staging
。<YOUR-USERNAME>
改成 Gitlab 帳號,<YOUR-ACCESS-TOKEN>
改成剛剛產生的 personal access token。-backend-config
設定的參數也可以寫在 backend block 裡,不過當然不建議把 token 寫進去。-migrate-state
會將目前 state 複製到新的 backend 也就是 Gitlab 去。下這個指令後會問你你要不要把現在在 local 的 state 轉移到新的 backend,我們就是要這麼做~輸入 yes
。
完成後,我們可以到 Gitlab 的 Terraform states 頁面看到轉移過去的 state:
右邊點點點裡的 Download JSON 可以下載 state 的 JSON 檔案,確認 state 有正確進入 Gitlab 後就可以把 local 的 terraform.tfstate*
刪除。
之後我們對 terraform 的操作都會存取 Gitlab 上的 state 檔案,所以在操作 terraform 時必須要連得到 Gitlab。
為了避免多人協作造成 conflict,terraform 會在 apply 前將 state lock 起來,執行完才 unlock。state 在 locking 期間,另一個人想 apply 必須等到 state 的 lock 解開。之前 backend 是 local 檔案,幾乎是無法協作的,即使用某些同步檔案的方式也會有 conflict 的問題。backend 換成 Gitlab 後,terraform 就能對 state 做 lock 跟 unlock,讓我們可以多人使用 terraform 協作。
lock 中的 terraform state 會顯示 Locked:
有時候如果沒有適當結束 terraform 操作,會導致 state 卡在 Locked 但其實沒有人正在修改它。這時候只要確認不是真的有人在改 state,可以從 Actions 裡 unlock state。
Gitlab 網頁介面目前還只能下載最新版本的 state,如果想取得舊版本的 state 得用 API。什麼時候會用到這個功能呢?像是在一陣混亂的操作後,不小心把 state 給清空了……需要舊版本救援……
取得某個版本的 state 的 curl 指令如下:
$ curl --header "Private-Token: [PERSONAL_TOKEN]" \
"https://[GITLAB_BASE_URL]/api/v4/projects/[PROJECT_ID]/terraform/state/[STATE_NAME]/versions/[VERSION_SERIAL]"
其中 [PERSONAL_TOKEN]
要換成自己的 personal token,token 的 scope 有 api 而且帳號至少有這個 project 的 developer 就能夠取得 state file。[GITLAB_BASE_URL]
則是使用的 Gitlab 的網址,如果是在 Gitlab 官方網站就是 gitlab.com
。[PROJECT_ID]
是放 terraform state 的 Gitlab project 的 id,可以從 project 首頁看到。[STATE_NAME]
是想取得的 state 的名稱,例如 staging
。最後的 [VERSION_SERIAL]
就是 state 的版本了,它是一個流水號,目前(2023-10)沒有另外的 API 或介面可以看到有哪些版本號,一個小方法是到 Gitlab Terraform state 頁面,從 action 中的 Download JSON 的網址可以看到最新版本號。