この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
「CodePipelineでECSをローリング更新した時に、Terraformと実際のリビジョンがずれてplanやapply時に差異が出る」
TerraformでECSを管理して、デプロイは他のツールを使っているパターンがあると思います。(例: CodePipeline,GithubActions,CircleCI等)
TerraformでECSを作成して、コンテナのデプロイにはCodePipeline(ローリング更新)を使っていました。
CodePipelineとTerraformタスク定義のリビジョンがズレてしまって、Terrformを変更していないのに terraform plan の際に差分が出てしまうことがありました。
結論: dataを使ってECSタスク定義の最新のarnを取得しましょう。
resource "aws_ecs_service" "mongo" {
name = "mongo"
cluster = aws_ecs_cluster.foo.id
desired_count = 2
# Track the latest ACTIVE revision
task_definition = data.aws_ecs_task_definition.mongo.arn
}
aws_ecs_task_definition | Data Sources | hashicorp/aws | Terraform Registry
事象: タスク定義のリビジョンがずれる
タスク定義のリビジョンがずれるパターンのECSのtfファイルです。
ecs.tf
resource "aws_ecs_cluster" "this" {
name = "${local.name_prefix}-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
}
resource "aws_ecs_service" "sample_app" {
name = local.name_prefix
cluster = aws_ecs_cluster.this.arn
launch_type = "FARGATE"
# terraformで作成したタスク定義のARNを直接指定
task_definition = aws_ecs_task_definition.sample_app.arn
desired_count = 1
platform_version = "1.4.0"
network_configuration {
assign_public_ip = false
security_groups = [module.ecs_sg.security_group_id]
subnets = module.vpc.private_subnets
}
load_balancer {
target_group_arn = aws_lb_target_group.sample_app.arn
container_name = "httpd"
container_port = 80
}
deployment_circuit_breaker {
enable = true
rollback = true
}
lifecycle {
ignore_changes = [ desired_count ]
}
}
resource "aws_ecs_task_definition" "sample_app" {
family = local.name_prefix
cpu = 256
memory = 512
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
container_definitions = templatefile("./file/container_definitions.json", {
ecr_repository_url : aws_ecr_repository.httpd.repository_url,
})
execution_role_arn = aws_iam_role.ecs_tasks.arn
}
このファイルを使って、リソースを作成してCodePipelineでローリング更新を行います。
その後、terraform側のコードは変更せず terraform planで差分を確認します。
$ terraform plan
# 出力結果から抜粋
# aws_ecs_service.sample_app will be updated in-place
~ resource "aws_ecs_service" "sample_app" {
id = "arn:aws:ecs:ap-northeast-1:00000000000:service/ecs-rolling-update-cluster/ecs-rolling-update"
name = "ecs-rolling-update"
tags = {}
~ task_definition = "arn:aws:ecs:ap-northeast-1:00000000000:task-definition/ecs-rolling-update:12" -> "arn:aws:ecs:ap-northeast-1:00000000000:task-definition/ecs-rolling-update:11"
# (14 unchanged attributes hidden)
# (4 unchanged blocks hidden)
}
terraform側でデプロイしたタスク定義のリビジョンが11で、CodePipelineでローリング更新した時に12になったため差分が発生しています。
解決策: dataを使って最新のタスク定義を取得するように取得するように修正
サービスにタスク定義を渡す部分を変更して、dataから最新のタスク定義を取得するようにします。
ecs.tf
resource "aws_ecs_cluster" "this" {
name = "${local.name_prefix}-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
}
resource "aws_ecs_service" "sample_app" {
name = local.name_prefix
cluster = aws_ecs_cluster.this.arn
launch_type = "FARGATE"
# CodePipelineでデプロイ時にリビジョンが更新されるため、最新のrevisionをdataで取得
task_definition = data.aws_ecs_task_definition.sample_app.arn
desired_count = 1
platform_version = "1.4.0"
network_configuration {
assign_public_ip = false
security_groups = [module.ecs_sg.security_group_id]
subnets = module.vpc.private_subnets
}
load_balancer {
target_group_arn = aws_lb_target_group.sample_app.arn
container_name = "httpd"
container_port = 80
}
deployment_circuit_breaker {
enable = true
rollback = true
}
lifecycle {
ignore_changes = [desired_count]
}
}
resource "aws_ecs_task_definition" "sample_app" {
family = local.name_prefix
cpu = 256
memory = 512
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
container_definitions = templatefile("./file/container_definitions.json", {
ecr_repository_url : aws_ecr_repository.httpd.repository_url,
})
execution_role_arn = aws_iam_role.ecs_tasks.arn
}
data "aws_ecs_task_definition" "sample_app" {
task_definition = aws_ecs_task_definition.sample_app.family
}
差分が出るパターンと同様の環境で、planを実行して差分が出ないことを確認できました。
$ terraform plan
補足: aws_ecs_serviceのlifecycleのignore_changesにタスク定義を含めるパターン
ECS Service側でタスク定義をlifecycleでignore_changesにすることで、差分が出なくはなります。
今回はタスク定義をTerraformで管理しているためTerraform側で変更した時にはECSのサービスも更新したかったため、dataを使いました。
ローリング更新の場合、CodeBuild上でコンテナをビルドしてファイル(imagedefinitions.json)にイメージのURIを書き込みます。 そのファイルを元に、タスク定義のイメージの部分を更新します。
この際に更新対象となるタスク定義は、サービスで稼働しているタスク定義になります。
lifecycleの方法では、タスク定義のリビジョンは増えますが稼働しているサービスのタスク定義は変わりません。 (更新したタスク定義を使って、CodePipelineでデプロイできない)
そのため、タスク定義を更新した場合はサービスのタスク定義を手動で更新する必要があります。
おわりに
Terraform管理のタスク定義をTerraform以外でECSデプロイするとリビジョンがずれる問題でした。
AWS Providerバージョン 3.70 まではdataのAttributesにarnが無かったため、以下の書き方をする必要がありました。
現在は、arnも追加されてスッキリ書けるようになっています。
resource "aws_ecs_service" "mongo" {
name = "mongo"
cluster = aws_ecs_cluster.foo.id
desired_count = 2
# Track the latest ACTIVE revision
task_definition = "${aws_ecs_task_definition.mongo.family}:${max(aws_ecs_task_definition.mongo.revision, data.aws_ecs_task_definition.mongo.revision)}"
}
この件でサンプルコードの1行修正ですが、terraform-provider-awsにコントリビュートしました。 今後もチャンスがあれば、貢献していきたいです。
以上、AWS事業本部の佐藤(@chari7311)でした。