如何使用 CODE_DEPLOY 部署控制器更新现有 ECS 服务的desired_count?

Jim*_*rts 5 terraform terraform-provider-aws

当我 update 时desired_count,terraform planner 显示该操作将是就地更新。但是,当 terraform 尝试应用更改时,我收到以下错误:

Terraform v0.12.21
Initializing plugins and modules...
2020/03/05 22:10:52 [DEBUG] Using modified User-Agent: Terraform/0.12.21 TFC/8f5a579db5
module.web.aws_ecs_service.web[0]: Modifying... [id=arn:aws:ecs:us-east-1:55555:service/web/web]

Error: Error updating ECS Service (arn:aws:ecs:us-east-1:55555:service/web/web): InvalidParameterException: Unable to update network parameters on services with a CODE_DEPLOY deployment controller. Use AWS CodeDeploy to trigger a new deployment.
Run Code Online (Sandbox Code Playgroud)

用于重现此情况的 terraform 代码如下所示:

resource "aws_lb" "platform" {
  name               = "platform"
  internal           = false
  load_balancer_type = "application"
  ip_address_type    = "ipv4"
  security_groups    = [aws_security_group.lb.id]
  subnets            = [for subnet in aws_subnet.lb : subnet.id]

  enable_deletion_protection = true

  tags = {
    Name = "platform"
    Type = "Public"
  }
}

resource "aws_lb_target_group" "platform" {
  count = 2

  name        = "platform-tg-${count.index + 1}"
  vpc_id      = var.vpc_id
  protocol    = "HTTP"
  port        = 80
  target_type = "ip"

  stickiness {
    type    = "lb_cookie"
    enabled = false
  }

  health_check {
    path                = "/healthcheck"
    port                = var.container_port
    protocol            = "HTTP"
    timeout             = 5
    healthy_threshold   = 5
    unhealthy_threshold = 3
    matcher             = "200"
  }

  tags = {
    Name = "platform-tg-${count.index + 1}"
    Type = "Public"
  }
}

resource "aws_lb_listener" "platform-https" {
  load_balancer_arn = aws_lb.platform.arn
  port              = 443
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"
  certificate_arn   = var.certificate_arn

  depends_on = [aws_lb_target_group.platform]

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.platform[0].arn
  }

  lifecycle {
    ignore_changes = [
      default_action
    ]
  }
}

locals {
  family         = "platform"
  container_name = "web"
}

resource "aws_cloudwatch_log_group" "platform" {
  name              = "/aws/ecs/platform"
  retention_in_days = 3653

  tags = {
    Name = "platform"
  }
}

resource "aws_ecs_task_definition" "platform" {
  family                   = local.family
  requires_compatibilities = ["FARGATE"]
  cpu                      = var.service.cpu
  memory                   = var.service.memory
  network_mode             = "awsvpc"
  execution_role_arn       = aws_iam_role.ecs_task_execution.arn
  task_role_arn            = aws_iam_role.ecs_task_execution.arn
  container_definitions = jsonencode(
    jsondecode(
      templatefile("${path.module}/taskdef.json", {
        family         = local.family
        container_name = local.container_name
        region         = var.region
        account_id     = var.account_id
        cpu            = var.service.cpu
        memory         = var.service.memory
        image          = var.service.container_image
        log_group      = aws_cloudwatch_log_group.platform.name
        node_env       = var.node_env
        port           = var.container_port
        platform_url   = var.platform_url
        short_url      = var.short_url
        cdn_url        = var.cdn_url
      })
    ).containerDefinitions
  )

  tags = {
    Name = "platform"
    Type = "Private"
  }
}

resource "aws_ecs_cluster" "platform" {
  name = "platform"

  setting {
    name  = "containerInsights"
    value = "enabled"
  }

  tags = {
    Name = "platform"
    Type = "Public"
  }
}

data "aws_lb_listener" "current-platform" {
  arn = aws_lb_listener.platform-https.arn
}

data "aws_ecs_task_definition" "current-platform" {
  task_definition = local.family
}

resource "aws_ecs_service" "platform" {
  count                   = var.delete_platform_ecs_service ? 0 : 1
  name                    = "platform"
  cluster                 = aws_ecs_cluster.platform.arn
  launch_type             = "FARGATE"
  desired_count           = var.service.container_count
  enable_ecs_managed_tags = true
  task_definition         = "${aws_ecs_task_definition.platform.family}:${max(aws_ecs_task_definition.platform.revision, data.aws_ecs_task_definition.current-platform.revision)}"

  depends_on = [aws_lb_target_group.platform]

  load_balancer {
    target_group_arn = data.aws_lb_listener.current-platform.default_action[0].target_group_arn
    container_name   = local.container_name
    container_port   = var.container_port
  }

  network_configuration {
    subnets         = sort([for subnet in aws_subnet.ecs : subnet.id])
    security_groups = [aws_security_group.ecs.id]
  }

  deployment_controller {
    type = "CODE_DEPLOY"
  }

  lifecycle {
    // NOTE: Based on: https://docs.aws.amazon.com/cli/latest/reference/ecs/update-service.html
    // If the network configuration, platform version, or task definition need to be updated, a new AWS CodeDeploy deployment should be created.
    ignore_changes = [
      load_balancer,
      network_configuration,
      task_definition
    ]
  }

  tags = {
    Name = "platform"
    Type = "Private"
  }
}

Run Code Online (Sandbox Code Playgroud)

这是使用 Terraform v0.12.21。完整的调试输出位于:https://gist.github.com/jgeurts/f4d930608a119e9cd75a7a54b111ee7c

Jim*_*rts 3

这可能不是最好的答案,但我无法让 terraform 仅调整所需的计数。相反,我向 ECS 服务添加了自动缩放:

忽略desired_count

resource "aws_ecs_service" "platform" {
  ...

  lifecycle {
    // NOTE: Based on: https://docs.aws.amazon.com/cli/latest/reference/ecs/update-service.html
    // If the network configuration, platform version, or task definition need to be updated, a new AWS CodeDeploy deployment should be created.
    ignore_changes = [
      desired_count, # Preserve desired count when updating an autoscaled ECS Service
      load_balancer,
      network_configuration,
      task_definition,
    ]
  }
}
Run Code Online (Sandbox Code Playgroud)

添加自动缩放:

resource "aws_appautoscaling_target" "platform" {
  max_capacity       = var.max_capacity
  min_capacity       = var.min_capacity
  resource_id        = "service/${aws_ecs_cluster.platform.name}/${aws_ecs_cluster.platform.name}"
  scalable_dimension = "ecs:service:DesiredCount"
  service_namespace  = "ecs"

  depends_on = [
    aws_ecs_cluster.platform,
  ]
}

resource "aws_appautoscaling_policy" "platform" {
  name               = "platform-auto-scale"
  service_namespace  = aws_appautoscaling_target.platform.service_namespace
  resource_id        = aws_appautoscaling_target.platform.resource_id
  scalable_dimension = aws_appautoscaling_target.platform.scalable_dimension
  policy_type        = "TargetTrackingScaling"

  target_tracking_scaling_policy_configuration {
    target_value       = var.service.autoscale_target_cpu_percentage
    scale_out_cooldown = 60
    scale_in_cooldown  = 300

    predefined_metric_specification {
      predefined_metric_type = "ECSServiceAverageCPUUtilization"
    }
  }
}

resource "aws_appautoscaling_scheduled_action" "platform_0430_increase_min_capacity" {
  name               = "platform-0430-increase-min-capacity"
  schedule           = "cron(30 4 * * ? *)"
  service_namespace  = aws_appautoscaling_target.platform.service_namespace
  resource_id        = aws_appautoscaling_target.platform.resource_id
  scalable_dimension = aws_appautoscaling_target.platform.scalable_dimension

  scalable_target_action {
    min_capacity = var.min_capacity + 4
    max_capacity = var.max_capacity
  }
}

resource "aws_appautoscaling_scheduled_action" "platform_0615_restore_min_capacity" {
  name               = "platform-0615-restore-min-capacity"
  schedule           = "cron(15 06 * * ? *)"
  service_namespace  = aws_appautoscaling_target.platform.service_namespace
  resource_id        = aws_appautoscaling_target.platform.resource_id
  scalable_dimension = aws_appautoscaling_target.platform.scalable_dimension

  scalable_target_action {
    min_capacity = var.min_capacity
    max_capacity = var.max_capacity
  }
}

resource "aws_appautoscaling_scheduled_action" "platform_weekday_0945_increase_min_capacity" {
  name               = "platform-weekday-0945-increase-min-capacity"
  schedule           = "cron(45 9 ? * MON-FRI *)"
  service_namespace  = aws_appautoscaling_target.platform.service_namespace
  resource_id        = aws_appautoscaling_target.platform.resource_id
  scalable_dimension = aws_appautoscaling_target.platform.scalable_dimension

  scalable_target_action {
    min_capacity = var.min_capacity + 4
    max_capacity = var.max_capacity
  }
}

resource "aws_appautoscaling_scheduled_action" "platform_weekday_2100_restore_min_capacity" {
  name               = "platform-weekday-2100-restore-min-capacity"
  schedule           = "cron(0 2100 ? * MON-FRI *)"
  service_namespace  = aws_appautoscaling_target.platform.service_namespace
  resource_id        = aws_appautoscaling_target.platform.resource_id
  scalable_dimension = aws_appautoscaling_target.platform.scalable_dimension

  scalable_target_action {
    min_capacity = var.min_capacity
    max_capacity = var.max_capacity
  }
}
Run Code Online (Sandbox Code Playgroud)