Maciej
Maciej

Reputation: 1499

Terraform error - ECS using spot instances to host containers

Sorry for long post but hope that will provide good background. Do not know if that is a bug or my code is wrong. I want to create ECS cluster with EC2 spot instances with help of launch template and ASG. My code is as follows:

For ECS service, cluster, task definition:

resource "aws_ecs_cluster" "main" {
  name = "test-ecs-cluster"
}

resource "aws_ecs_service" "ec2_service" {
  for_each = data.aws_subnet_ids.all_subnets.ids
  name                              = "myservice_${replace(timestamp(), ":", "-")}"
  task_definition                   = aws_ecs_task_definition.task_definition.arn
  cluster                           = aws_ecs_cluster.main.id
  desired_count                     = 1
  launch_type                       = "EC2"
  health_check_grace_period_seconds = 10

  load_balancer {
    container_name   = "test-container"
    container_port   = 80
    target_group_arn = aws_lb_target_group.alb_ec2_ecs_tg.id
  }

  network_configuration {
    security_groups  = [aws_security_group.ecs_ec2.id]
    subnets          = [each.value]
    assign_public_ip = "false"
  }

  ordered_placement_strategy {
    type  = "binpack"
    field = "cpu"
  }
}

resource "aws_ecs_task_definition" "task_definition" {
  container_definitions    = data.template_file.task_definition_template.rendered
  family                   = "test-ec2-task-family"
  execution_role_arn       = aws_iam_role.ecs_task_exec_role_ec2_ecs.arn
  task_role_arn            = aws_iam_role.ecs_task_exec_role_ec2_ecs.arn
  network_mode             = "awsvpc"
  memory                   = 1024
  cpu                      = 1024
  requires_compatibilities = ["EC2"]

  lifecycle {
    create_before_destroy = true
  }
}

data "template_file" "task_definition_template" {
  template = file("${path.module}/templates/task_definition.json.tpl")
  vars = {
    container_port = var.container_port
    region         = var.region
    log_group      = var.cloudwatch_log_group
  }
}

Launch template:

resource "aws_launch_template" "template_for_spot" {
  name = "test-spor-ecs-launch-template"
  disable_api_termination = false
  instance_type = "t3.small"
  image_id = data.aws_ami.amazon_linux_2_ecs_optimized.id
  key_name = "FrankfurtRegion"
  user_data = data.template_file.user_data.rendered
  vpc_security_group_ids = [aws_security_group.ecs_ec2.id]
  monitoring {
    enabled = var.enable_spot == "true" ? false : true
  }
  block_device_mappings {
    device_name = "/dev/sda1"
    ebs {
      volume_size = 30
    }
  }
  iam_instance_profile {
    arn = aws_iam_instance_profile.ecs_instance_profile.arn
  }
  lifecycle {
    create_before_destroy = true
  }
}

data "template_file" "user_data" {
  template = file("${path.module}/user_data.tpl")
  vars = {
    cluster_name = aws_ecs_cluster.main.name
  }
}

ASG with scaling policy:

resource "aws_autoscaling_group" "ecs_spot_asg" {
  name = "test-asg-for-ecs"
  max_size = 4
  min_size = 2
  desired_capacity = 2
  termination_policies = [
    "OldestInstance"]
  vpc_zone_identifier = data.aws_subnet_ids.all_subnets.ids
  health_check_type = "ELB"
  health_check_grace_period = 300

  mixed_instances_policy {
    instances_distribution {
      on_demand_percentage_above_base_capacity = 0
      spot_instance_pools = 2
      spot_max_price = "0.03"
    }
    launch_template {
      launch_template_specification {
        launch_template_id = aws_launch_template.template_for_spot.id
        version = "$Latest"
      }
      override {
        instance_type = "t3.large"
      }
      override {
        instance_type = "t3.medium"
      }
      override {
        instance_type = "t3a.large"
      }
      override {
        instance_type = "t3a.medium"
      }
    }
  }
  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_autoscaling_policy" "ecs_cluster_scale_policy" {
  autoscaling_group_name = aws_autoscaling_group.ecs_spot_asg.name
  name = "test-ecs-cluster-scaling-policy"
  policy_type = "TargetTrackingScaling"
  adjustment_type = "ChangeInCapacity"

  target_tracking_configuration {
    target_value = 70
    customized_metric_specification {
      metric_name = "ECS-cluster-metric"
      namespace = "AWS/ECS"
      statistic = "Average"
      metric_dimension {
        name = aws_ecs_cluster.main.name
        value = aws_ecs_cluster.main.name
      }
    }
  }
}

EDIT: I'm getting :

Error: InvalidParameterException: Creation of service was not idempotent. "test-ec2-service-qaz"

on ecs.tf line 5, in resource "aws_ecs_service" "ec2_service": 5: resource "aws_ecs_service" "ec2_service" {

EDIT2: Changed ecs_service name to name = "myservice_${replace(timestamp(), ":", "-")}", still getting same error.

Was reading from other issues that it is becouse of usage lifecycle with create_before_destroy statement in ecs_service, but it is not declared in my code. Maybe it is something related to something else, can't say what.

Upvotes: 0

Views: 1558

Answers (1)

Maciej
Maciej

Reputation: 1499

Thanks @Marko E and @karnauskas on github with name = "myservice_${each.value}" was able to deploy three ECS services. With correction to sub-nets handling I was able to deploy all the "stuff" as required. Subnets:

data "aws_subnet_ids" "all_subnets" {
  vpc_id = data.aws_vpc.default.id
}

data "aws_subnet" "subnets" {
  for_each = data.aws_subnet_ids.all_subnets.ids
  id = each.value
}

Upvotes: 1

Related Questions