2021年9月22日 星期三

Terraform - will be read during apply ?

有時候會在 terraform plan / apply 的時候看到這種恐怖的情況...

  # module.foobar.data.google_service_account.this will be read during apply
  # (config refers to values not yet known)
  ...
  # module.foobar.google_pubsub_subscription.this must be replaced
  ...
  # module.foobar.google_pubsub_topic.this must be replaced
  ...
  # module.foobar.google_storage_bucket.this will be destroyed
  ...
  # module.foobar.google_storage_bucket_iam_member.bucket will be destroyed
  ...
  # module.foobar.google_storage_bucket_iam_member.object will be destroyed
  ...
  # module.foobar.google_pubsub_subscription_iam_member.this will be updated in-place
  ...

在同一個層級(module.foobar), 只要有一個 data object 需要被重讀(will be read during apply)更新內容的話, 後面同級的 resource 幾乎都會被當作受到影響, 而被 terraform 進行取代(replaced)或是砍掉(destroyed)的處置, 要是這些 resource 是 pubsub topic / cloud storage bucket 這類會存資料的地方, 那就會掉資料甚至全滅.

如果直接 terraform apply -target='module.foobar' 執行更新下去, 那通常會很慘, 因為 terraform apply 列出來這些就是這次安排執行的處置, 就算 module.foobar.data.google_service_account.this 執行重讀之後內容還是一樣, 後續的取代或砍掉的處置照樣會執行, 造成誤砍誤殺的結果.

正確的手動解法, 是執行 terraform apply -target='module.foobar.data.google_service_account.this' 先把 data read 的動作獨自執行完成, 若是 data read 回來的內容不變, 那再執行 terraform plan / apply 就不會出現後續的取代或砍掉的處置. 若是內容有變, 顯示出來的可能就只是 object 裡面部分更新(will be updated in-place)的處置, 影響太大的才會被取代或是砍掉.

至於為什麼會出現需要重讀的情況, 大都是因為線上環境的設定跟 terraform 的設定不一致, 或是 gke 被更新版本導致 resource 被異動, 或者可能是 data.google_service_account.this 資料過期了, 所以需要先更新資料.

2021年6月21日 星期一

Terraform - GCP IAM apply / destroy race condition

這是一個用 terraform 處理 GCP project / pubsub subscription / pubsub topic / storage bucket / etc 的 IAM role 常會遇到的 race condition, 目前高達八成確定原因是在 GCP 的相關 API 不是 single action, 而是 atomic action, 而且設後不理沒確認是否執行完成就直接 return.

目前只能用 workaround 解法: 再執行/多執行幾次 terraform 指令. (原因後敘)

假設狀況如下: (真實狀況可能不是/不只這樣)
- bucket foobar 原本就有 role: roles/storage.legacyBucketReader
- 現在要改成 role: roles/storage.legacyBucketOwner
- 執行 terraform apply (無關的部分就省略了):
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: -/+ destroy and then create replacement Terraform will perform the following actions: # module.basement.google_storage_bucket_iam_member.bucket must be replaced -/+ resource "google_storage_bucket_iam_member" "bucket" { ~ role = "roles/storage.legacyBucketReader" -> "roles/storage.legacyBucketOwner" # forces replacement } Plan: 1 to add, 0 to change, 1 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value:
表示這個 bucket 的 iam role 會被先拆後建. - 輸入 yes 再按 enter 下去之後, 噴錯誤訊息出來說無法設定之類的.... (省略) - 通常只要再執行一次 terraform apply 再 yes 下去之後就可以正常執行完. - 還是噴一樣錯誤訊息的話, 那就再等一下再執行一次... - The End.

有人會問, 是不是可以用 time_sleep 的寫法讓 destroy 先執行完再 apply ?

首先, 這是一個 resource 被 replace (destroy -> apply) 的動作, 並不是分開的 resource 運作, 所以不適用上面這種方式來處理.

其次, 在 terraform 裡面這應該是個呼叫 GCP API 進行 remove 之後再 add 的行為, 中間沒有也不應該有 delay 動作影響執行效率(這也可能產生別的 race condition), 問題點是在 remove 跟 add 大概是 atomic action, 沒有全部確定執行完就 return 回來, 產生後續的 race condition 問題.

最後, 利用 time_sleep 那個寫法實在是累贅也有問題, 因為使用者通常不會知道 create 要花多久時間(create_duration), 也不會知道 destroy_duration 多久, 只能用預估或是猜的來設定. 若是 GCP 不忙的時候可能 1s 就全部跑完, 卻還要等完剩下的 29s, 或是 1m 才跑完, 設定 30s 照樣還是發生 race condition.

倒不如還是人工 delay 再人工執行同樣的 terraform 指令還比較簡單實用.

2021年5月31日 星期一

GCP - Basic.Owner is not full Owner of project

一般來說, GCP project 使用者最大的權限應該是 Basic.Owner, 幾乎所有情況都能通行無阻.

但是...

最好再加上一個 Storage Admin 的 Owner, 才不會在 Cloud Storage 之類的相關功能遇到莫名其妙的卡住. 尤其是有用 terraform 操控 bucket + iam 的時候...

如果 project 上面還有一層 organization, 最好也設定個 Organization 的 Owner 上身, 避免靈異現象...