在首爾成功 apply resource 後,我們回到東京 region 對 configuration 做最後的修正。
先把之前移到別的資料夾的 state 檔案 terraform.tfstate
跟 terraform.tfstate.backup
搬回來,import 之前漏掉的 resource,然後用 terraform plan
檢查 cloud 上的 resource 跟 terraform configuration 有什麼差異、修正讓兩者一致,這樣才完成了轉移到 terraform。
前兩天我們在首爾的驗證過程中有多建立一些 resource:給 Codepipeline 放 artifact 的 s3 bucket bucket 跟 EventBridge rule,要把它們 import 回來。因為已經寫了 resource block 們,這邊我們一樣用 terraform import
這個老方法~~,順便多感受一下 import block 的好~~
$ terraform import aws_iam_role.codepipeline_trigger cwe-role-ap-northeast-1-my-app
import 完用 terraform plan
可以檢查 terraform configuration 跟 cloud 實際 resource 的差別:
把 configuration 改成跟 AWS 上 resource 符合,讓它不會有 changes。
接著發現東京 region 在 codepipeine trigger 的 IAM role 跟 policy resource 跟筆者自己寫的不一樣,筆者是直接用一個 aws_iam_role_policy
resource 在 role 裡寫 inline policy,但 web console 是建立一個 IAM policy 再 attach 給 role。這邊我們還是以 cloud 上實際有的 resource 為主,不同的 resource 是不能 import 的,所以把 aws_iam_role_policy.codepipeline_trigger
改成 aws_iam_policy.codepipeline_trigger
(要去掉 role
attribute)再 import:
$ terraform import aws_iam_policy.codepipeline_trigger arn:aws:iam::xxxxxxxx:policy/service-role/start-pipeline-execution-ap-northeast-1-my-app
import 後一樣 plan 看差異、修正 configuration。
改用 aws_iam_policy
就需要多 import aws_iam_role_policy_attachment
resource,也就是 IAM role 與 policy 的關聯。我們可以直接寫以下 resource block 然後 import:
resource "aws_iam_role_policy_attachment" "codepipeline_trigger" {
role = aws_iam_role.codepipeline_trigger.name
policy_arn = aws_iam_policy.codepipeline_trigger.arn
}
$ terraform import aws_iam_role_policy_attachment.codepipeline_trigger cwe-role-ap-northeast-1-my-app/arn:aws:iam::xxxxxxxxx:policy/service-role/start-pipeline-execution-ap-northeast-1-my-app
$ terraform import aws_cloudwatch_event_rule.codepipeline_trigger codepipeline-myapp-latest-447280-rule
一樣是修改 terraform configuration,讓它跟真實 resource 保持一致,所以這邊我們會把 role_arn
attribute 去掉。
event target 需要用到 event_bus_name/rule-name/target-id
來 import,其中 target id 在 web console 上看不到,要用 awscli events list-targets-by-rule 指令查:
$ aws events list-targets-by-rule --rule codepipeline-myapp-latest-447280-rule
{
"Targets": [
{
"Id": "codepipeline-my-app",
"Arn": "arn:aws:codepipeline:ap-northeast-1:384137370331:my-app",
"RoleArn": "arn:aws:iam::xxxxxxxxx:role/service-role/cwe-role-ap-northeast-1-my-app"
}
]
}
查到 target id 後 import:
$ terraform import aws_cloudwatch_event_target.codepipeline_trigger default/codepipeline-myapp-latest-447280-rule/codepipeline-my-app
最後是用來放 codepipeline artifact 的 s3 bucket 也要 import:
$ terraform import aws_s3_bucket.codepipeline_artifact codepipeline-ap-northeast-1-239650800043
import 完之前漏的 resource 後,我們最後要用 terraform plan
看還有什麼 changes,把 configuration 修正成跟 cloud 上實際 resource 一致。
Day 18 我們把預設的 security group 改成用 resource aws_default_security_group
,所以這邊要先 remove state 再重新 import:
$ terraform state rm aws_security_group.default
$ terraform import aws_default_security_group.default sg-0920ca3925dc173a2
terraform state rm [RESOURCE]
是把這個 resource 從 terraform state 中移除,也就是 terraform 不再管理這個 resource,之後的 plan、apply 跟 destroy 都不會影響這個 resource。所以如果有不想再用 terraform 管理的 resource,把 resource 從 terraform state 中移除即可。這裡我們是要把原本以 resource aws_security_group
管理的 default security group 改成用 aws_default_security_group
來管理,所以先移除舊的 state,再用 import 把同一個 security group 跟 aws_default_security_group
resource 對應起來。
既然 terraform import
跟 terraform state rm
都說到了,順便介紹另外幾個跟 state 有關的操作指令:
terraform state list
:列出所有 terraform 管理的 resource
terraform state show [RESOURCE]
:顯示某個 resource 的詳細資料
terraform state mv [SOURCE] [DESTINATION]
:rename resource
terraform state list
跟 terraform state show
很好理解,是列出清單跟顯示詳細資訊。
terraform state mv
會用在我們想幫 resource 改名稱的時候,例如想把 aws_security_group.allow_http_from_internet
的名稱改成 allow_web_from_internet
,我們要做兩件事:
修改 configuration 中 resource block 的名稱
terraform state mv aws_security_group.allow_http_from_internet aws_security_group.allow_web_from_internet
修改 terraform state 中記錄的 resource 名稱
如果 state 沒有跟著修改,plan / apply 會看到 destroy 原本的 resource 然後新增新的 resource,這可能不是我們希望的動作,security group 刪掉重建可能還好,但總不能把 RDS 的資料庫砍掉重建…… 😨
不知道各位在前面 terraform apply
的過程中有沒有注意到有時後會有像下面這樣的 changes:
它會在 apply 或 plan 的時候出現,但其實不是我們真的要改動 task definition,雖然直接 apply 無傷大雅但常常出現也是挺惱人的(會需要多一點點判斷才能確認改 terraform 沒有改到實際 resource),我們來說一下這是怎麼回事以及如何讓它不會在不需要出現 changes 的時候一直冒出來。
從 changes 可以看到 ecs service 的 task_definition 從 my-app:5
改成 my-app
,在 resource 裡我們是這樣寫的:
task_definition = aws_ecs_task_definition.td.family
所以它想改成 my-app
是正確的,問題在為什麼 resource 上會變成 my-app:5
?
我們進到 ecs service 的 configuration 可以看到 task 是:
我們明明沒有改 task definition,怎麼會出現新的 revision 呢?觀察看看不同 revision 的差別,會發現它們 container 的 image 最後面的 sha256 是不同的,像是我的 revision 4 的 sha256 是 sha256:d99e2a0c654ffa0f3ac22e8892ecea879423dd043c879b7f3643ea5c1a3df3ac
、revision 5 則是 sha256:a90959084e3b58a1f815b363d4436677c8dad4bea75cdb8f3d95f5a13fb56005
。再回到 ECR repository,會發現目前 latest
tag 的 image 的 digest 正是 sha256:a90959084e3b58a1f815b363d4436677c8dad4bea75cdb8f3d95f5a13fb56005
,而前一個 tag 的 image digest 是 sha256:d99e2a0c654ffa0f3ac22e8892ecea879423dd043c879b7f3643ea5c1a3df3ac
。我們可以推測在 Gitlab 推新的 image 上 ECR 後,會有新的 task definition revision 產生來讓 ECS service 使用到最新版的 image。要驗證這個推測最簡單的方式當然是讓 Gitlab pipeline 再跑一次囉~
確認 task definition 會在 Gitlab pipeline deploy 時產生新的 revision,這是一個我們想要的行為沒錯,但又不希望 Gitlab 的 deployment 干擾到 terraform 的使用,這種狀況可以利用 terraform 的 data source 來避免一直出現不需要出現的 changes。
在官方文件中,data source 讓 terraform 可以使用其他人定義或修改的 resource,Gitlab pipeline deploy 造成的 task definition revision 更新便是屬於「其他人修改 resource」。在我們的例子裡,要加個 data source block:
data "aws_ecs_task_definition" "td" {
task_definition = aws_ecs_task_definition.td.family
}
並且把 aws_ecs_service.service
的 task_definition
改成:
task_definition = "${aws_ecs_task_definition.td.family}:${max("${aws_ecs_task_definition.td.revision}", "${data.aws_ecs_task_definition.td.revision}")}"
data source 會在 refresh
階段(會在 plan
前執行)讀取相關 resource 的值並更新到 state ,在 plan 跟 apply 時便能利用讀取到的值避免出現不必要的 changes,我們也用 max()
來確保 ECS service 是使用最新的 active revision。
最後我們來看一下整個從 Gitlab deploy 到 AWS ECS 的流程。
Gitlab Pipeline 會將 docker image push 進 ECR repository、更新 latest
tag 的 image,這時候 EventBridge 的 rule 被觸發接著 trigger CodePipeline 啟動。CodePipeline 從 ECR repository 拿到 docker image,最後 deploy 到 ECS 的 service。