From 01813df835bc2be37bc7dc4a6bd4ab9eb6efe4d8 Mon Sep 17 00:00:00 2001 From: "A.J. Brown" Date: Sun, 10 Jun 2018 14:30:29 -0400 Subject: [PATCH 1/2] Allow spot instances to be used An optional variable `spot_bid_price` has been added, which will allow spot instances to be used for the ECS cluster. When not specified (the default), standard on-demand instances will still be used. --- .gitignore | 2 ++ README.md | 4 +++- main.tf | 1 + variables.tf | 6 ++++++ 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0cc2124 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.iml +.idea/ diff --git a/README.md b/README.md index d4a971e..1f837c1 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ extra_tags = [ - `allowed_cidr_blocks` - List of subnets to allow into the ECS Security Group. Defaults to `["0.0.0.0/0"]`. - `ami` - A specific AMI image to use, eg `ami-95f8d2f3`. Defaults to the latest ECS optimized Amazon Linux AMI. - `ami_version` - Specific version of the Amazon ECS AMI to use (e.g. `2016.09`). Defaults to `*`. Ignored if `ami` is specified. -- `heartbeat_timeout` - Heartbeat Timeout setting for how long it takes for the graceful shutodwn hook takes to timeout. This is useful when deploying clustered applications like consul that benifit from having a deploy between autoscaling create/destroy actions. Defaults to 180" +- `heartbeat_timeout` - Heartbeat Timeout setting for how long it takes for the graceful shutdown hook takes to timeout. This is useful when deploying clustered applications like consul that benifit from having a deploy between autoscaling create/destroy actions. Defaults to 180" - `security_group_ids` - a list of security group IDs to apply to the launch configuration - `user_data` - The instance user data (e.g. a `cloud-init` config) to use in the `aws_launch_configuration` - custom_iam_policy - JSON containing the custom IAM policy for ECS nodes. Will overwrite the default one if set. @@ -56,6 +56,8 @@ extra_tags = [ - `consul_memory_reservation` - The soft limit (in MiB) of memory to reserve for the container, defaults 20 - `registrator_memory_reservation` - The soft limit (in MiB) of memory to reserve for the container, defaults 20 - `enable_agents` - Enable Consul Agent and Registrator tasks on each ECS Instance. Defaults to false +- `spot_bid_price` - Use spot instances and request this bid price. Note that with this option you risk your instances + shutting down if the market price rises above your bid price. Usage ----- diff --git a/main.tf b/main.tf index 99f1d01..47b5ba9 100644 --- a/main.tf +++ b/main.tf @@ -36,6 +36,7 @@ resource "aws_launch_configuration" "ecs" { iam_instance_profile = "${aws_iam_instance_profile.ecs_profile.name}" security_groups = ["${concat(list(aws_security_group.ecs.id), var.security_group_ids)}"] associate_public_ip_address = "${var.associate_public_ip_address}" + spot_price = "${var.spot_bid_price}" ebs_block_device { device_name = "${var.ebs_block_device}" diff --git a/variables.tf b/variables.tf index 9a12015..c81a8ee 100644 --- a/variables.tf +++ b/variables.tf @@ -56,6 +56,7 @@ variable "ebs_block_device" { } variable "extra_tags" { + type = "list" default = [] } @@ -127,6 +128,11 @@ variable "servers" { description = "The number of servers to launch." } +variable "spot_bid_price" { + default = "" + description = "If specified, spot instances will be requested at this bid price. If not specified, on-demand instances will be used." +} + variable "subnet_id" { type = "list" description = "The AWS Subnet ID in which you want to delpoy your instances" From 54cb6a62d882b13568276b81674ff01ff0a70a17 Mon Sep 17 00:00:00 2001 From: "A.J. Brown" Date: Sat, 29 Jun 2019 08:04:30 -0400 Subject: [PATCH 2/2] upgrade to support terraform 0.12 --- .gitignore | 1 + consul_agent.tf | 43 ++++++++-------- graceful_shutdown.tf | 5 +- iam.tf | 113 ++++++++++++++++++++++++++++--------------- main.tf | 87 ++++++++++++++++++--------------- outputs.tf | 25 +++++----- variables.tf | 15 +++--- versions.tf | 4 ++ 8 files changed, 172 insertions(+), 121 deletions(-) create mode 100644 versions.tf diff --git a/.gitignore b/.gitignore index 0cc2124..ff3f832 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.iml .idea/ +.terraform diff --git a/consul_agent.tf b/consul_agent.tf index 5f0da1c..a307f16 100644 --- a/consul_agent.tf +++ b/consul_agent.tf @@ -1,26 +1,26 @@ data "template_file" "consul" { - template = "${file("${path.module}/templates/consul.json")}" - - vars { - env = "${aws_ecs_cluster.cluster.name}" - image = "${var.consul_image}" - registrator_image = "${var.registrator_image}" - consul_memory_reservation = "${var.consul_memory_reservation}" - registrator_memory_reservation = "${var.registrator_memory_reservation}" + template = file("${path.module}/templates/consul.json") + + vars = { + env = aws_ecs_cluster.cluster.name + image = var.consul_image + registrator_image = var.registrator_image + consul_memory_reservation = var.consul_memory_reservation + registrator_memory_reservation = var.registrator_memory_reservation awslogs_group = "consul-agent-${aws_ecs_cluster.cluster.name}" awslogs_stream_prefix = "consul-agent-${aws_ecs_cluster.cluster.name}" - awslogs_region = "${var.region}" + awslogs_region = var.region } } # End Data block resource "aws_ecs_task_definition" "consul" { - count = "${var.enable_agents ? 1 : 0}" + count = var.enable_agents ? 1 : 0 family = "consul-agent-${aws_ecs_cluster.cluster.name}" - container_definitions = "${data.template_file.consul.rendered}" + container_definitions = data.template_file.consul.rendered network_mode = "host" - task_role_arn = "${aws_iam_role.consul_task.arn}" + task_role_arn = aws_iam_role.consul_task[0].arn volume { name = "consul-config-dir" @@ -34,24 +34,25 @@ resource "aws_ecs_task_definition" "consul" { } resource "aws_cloudwatch_log_group" "consul" { - count = "${var.enable_agents ? 1 : 0}" - name = "${aws_ecs_task_definition.consul.family}" + count = var.enable_agents ? 1 : 0 + name = aws_ecs_task_definition.consul[0].family - tags { - VPC = "${data.aws_vpc.vpc.tags["Name"]}" - Application = "${aws_ecs_task_definition.consul.family}" + tags = { + VPC = data.aws_vpc.vpc.tags["Name"] + Application = aws_ecs_task_definition.consul[0].family } } resource "aws_ecs_service" "consul" { - count = "${var.enable_agents ? 1 : 0}" + count = var.enable_agents ? 1 : 0 name = "consul-agent-${aws_ecs_cluster.cluster.name}" - cluster = "${aws_ecs_cluster.cluster.id}" - task_definition = "${aws_ecs_task_definition.consul.arn}" - desired_count = "${var.servers}" + cluster = aws_ecs_cluster.cluster.id + task_definition = aws_ecs_task_definition.consul[0].arn + desired_count = var.servers deployment_minimum_healthy_percent = "60" placement_constraints { type = "distinctInstance" } } + diff --git a/graceful_shutdown.tf b/graceful_shutdown.tf index 676c0e3..d1ef923 100644 --- a/graceful_shutdown.tf +++ b/graceful_shutdown.tf @@ -3,8 +3,9 @@ resource "aws_autoscaling_lifecycle_hook" "graceful_shutdown_asg_hook" { name = "graceful_shutdown_asg" - autoscaling_group_name = "${aws_autoscaling_group.ecs.name}" + autoscaling_group_name = aws_autoscaling_group.ecs.name default_result = "CONTINUE" - heartbeat_timeout = "${var.heartbeat_timeout}" + heartbeat_timeout = var.heartbeat_timeout lifecycle_transition = "autoscaling:EC2_INSTANCE_TERMINATING" } + diff --git a/iam.tf b/iam.tf index bec1739..901fc25 100644 --- a/iam.tf +++ b/iam.tf @@ -1,12 +1,20 @@ resource "aws_iam_instance_profile" "ecs_profile" { - name_prefix = "${replace(format("%.102s", replace("tf-ECSProfile-${var.name}-", "_", "-")), "/\\s/", "-")}" - role = "${aws_iam_role.ecs_role.name}" - path = "${var.iam_path}" + name_prefix = replace( + format("%.102s", replace("tf-ECSProfile-${var.name}-", "_", "-")), + "/\\s/", + "-", + ) + role = aws_iam_role.ecs_role.name + path = var.iam_path } resource "aws_iam_role" "ecs_role" { - name_prefix = "${replace(format("%.32s", replace("tf-ECSInRole-${var.name}-", "_", "-")), "/\\s/", "-")}" - path = "${var.iam_path}" + name_prefix = replace( + format("%.32s", replace("tf-ECSInRole-${var.name}-", "_", "-")), + "/\\s/", + "-", + ) + path = var.iam_path assume_role_policy = < 0 ? 0 : 1 policy = < 0 ? 1 : 0 + +policy = var.custom_iam_policy } resource "aws_iam_policy_attachment" "attach_ecs" { - name = "ecs-attachment" - roles = ["${aws_iam_role.ecs_role.name}"] - policy_arn = "${element(concat(aws_iam_policy.ecs_policy.*.arn, aws_iam_policy.custom_ecs_policy.*.arn), 0)}" +name = "ecs-attachment" +roles = [aws_iam_role.ecs_role.name] +policy_arn = element( +concat( +aws_iam_policy.ecs_policy.*.arn, +aws_iam_policy.custom_ecs_policy.*.arn, +), +0, +) } # IAM Resources for Consul and Registrator Agents data "aws_iam_policy_document" "consul_task_policy" { - statement { - actions = [ - "ec2:Describe*", - "autoscaling:Describe*", - ] - - resources = ["*"] - } +statement { +actions = [ +"ec2:Describe*", +"autoscaling:Describe*", +] + +resources = ["*"] +} } data "aws_iam_policy_document" "assume_role_consul_task" { - statement { - actions = ["sts:AssumeRole"] +statement { +actions = ["sts:AssumeRole"] - principals { - type = "Service" - identifiers = ["ecs-tasks.amazonaws.com"] - } - } +principals { +type = "Service" +identifiers = ["ecs-tasks.amazonaws.com"] +} +} } resource "aws_iam_role" "consul_task" { - count = "${var.enable_agents ? 1 : 0}" - name_prefix = "${replace(format("%.32s", replace("tf-agentTaskRole-${var.name}-", "_", "-")), "/\\s/", "-")}" - path = "${var.iam_path}" - assume_role_policy = "${data.aws_iam_policy_document.assume_role_consul_task.json}" +count = var.enable_agents ? 1 : 0 +name_prefix = replace( +format("%.32s", replace("tf-agentTaskRole-${var.name}-", "_", "-")), +"/\\s/", +"-", +) +path = var.iam_path +assume_role_policy = data.aws_iam_policy_document.assume_role_consul_task.json } resource "aws_iam_role_policy" "consul_ecs_task" { - count = "${var.enable_agents ? 1 : 0}" - name_prefix = "${replace(format("%.102s", replace("tf-agentTaskPol-${var.name}-", "_", "-")), "/\\s/", "-")}" - role = "${aws_iam_role.consul_task.id}" - policy = "${data.aws_iam_policy_document.consul_task_policy.json}" +count = var.enable_agents ? 1 : 0 +name_prefix = replace( +format("%.102s", replace("tf-agentTaskPol-${var.name}-", "_", "-")), +"/\\s/", +"-", +) +role = aws_iam_role.consul_task[0].id +policy = data.aws_iam_policy_document.consul_task_policy.json } + diff --git a/main.tf b/main.tf index f550420..1e1ba80 100644 --- a/main.tf +++ b/main.tf @@ -9,39 +9,47 @@ data "aws_ami" "ecs_ami" { } data "template_file" "user_data" { - template = "${file("${path.module}/templates/user_data.tpl")}" - - vars { - additional_user_data_script = "${var.additional_user_data_script}" - cluster_name = "${aws_ecs_cluster.cluster.name}" - docker_storage_size = "${var.docker_storage_size}" - dockerhub_token = "${var.dockerhub_token}" - dockerhub_email = "${var.dockerhub_email}" + template = file("${path.module}/templates/user_data.tpl") + + vars = { + additional_user_data_script = var.additional_user_data_script + cluster_name = aws_ecs_cluster.cluster.name + docker_storage_size = var.docker_storage_size + dockerhub_token = var.dockerhub_token + dockerhub_email = var.dockerhub_email } } data "aws_vpc" "vpc" { - id = "${var.vpc_id}" + id = var.vpc_id } resource "aws_launch_configuration" "ecs" { - name_prefix = "${coalesce(var.name_prefix, "ecs-${var.name}-")}" - image_id = "${var.ami == "" ? format("%s", data.aws_ami.ecs_ami.id) : var.ami}" # Workaround until 0.9.6 - instance_type = "${var.instance_type}" - key_name = "${var.key_name}" - iam_instance_profile = "${aws_iam_instance_profile.ecs_profile.name}" - security_groups = ["${concat(list(aws_security_group.ecs.id), var.security_group_ids)}"] - associate_public_ip_address = "${var.associate_public_ip_address}" - spot_price = "${var.spot_bid_price}" + name_prefix = coalesce(var.name_prefix, "ecs-${var.name}-") + image_id = var.ami == "" ? format("%s", data.aws_ami.ecs_ami.id) : var.ami # Workaround until 0.9.6 + instance_type = var.instance_type + key_name = var.key_name + iam_instance_profile = aws_iam_instance_profile.ecs_profile.name + # TF-UPGRADE-TODO: In Terraform v0.10 and earlier, it was sometimes necessary to + # force an interpolation expression to be interpreted as a list by wrapping it + # in an extra set of list brackets. That form was supported for compatibilty in + # v0.11, but is no longer supported in Terraform v0.12. + # + # If the expression in the following list itself returns a list, remove the + # brackets to avoid interpretation as a list of lists. If the expression + # returns a single list item then leave it as-is and remove this TODO comment. + security_groups = concat([aws_security_group.ecs.id], var.security_group_ids) + associate_public_ip_address = var.associate_public_ip_address + spot_price = var.spot_bid_price ebs_block_device { - device_name = "${var.ebs_block_device}" - volume_size = "${var.docker_storage_size}" + device_name = var.ebs_block_device + volume_size = var.docker_storage_size volume_type = "gp2" delete_on_termination = true } - user_data = "${coalesce(var.user_data, data.template_file.user_data.rendered)}" + user_data = coalesce(var.user_data, data.template_file.user_data.rendered) lifecycle { create_before_destroy = true @@ -50,22 +58,22 @@ resource "aws_launch_configuration" "ecs" { resource "aws_autoscaling_group" "ecs" { name_prefix = "asg-${aws_launch_configuration.ecs.name}-" - vpc_zone_identifier = ["${var.subnet_id}"] - launch_configuration = "${aws_launch_configuration.ecs.name}" - min_size = "${var.min_servers}" - max_size = "${var.max_servers}" - desired_capacity = "${var.servers}" + vpc_zone_identifier = var.subnet_id + launch_configuration = aws_launch_configuration.ecs.name + min_size = var.min_servers + max_size = var.max_servers + desired_capacity = var.servers termination_policies = ["OldestLaunchConfiguration", "ClosestToNextInstanceHour", "Default"] - load_balancers = ["${var.load_balancers}"] - enabled_metrics = ["${var.enabled_metrics}"] + load_balancers = var.load_balancers + enabled_metrics = var.enabled_metrics - tags = [{ - key = "Name" - value = "${var.name} ${var.tagName}" - propagate_at_launch = true - }] - - tags = ["${var.extra_tags}"] + tags = list( + { + key = "Name" + value = "${var.name} ${var.tagName}" + propagate_at_launch = true + } + , var.extra_tags) lifecycle { create_before_destroy = true @@ -79,20 +87,20 @@ resource "aws_autoscaling_group" "ecs" { resource "aws_security_group" "ecs" { name = "ecs-sg-${var.name}" description = "Container Instance Allowed Ports" - vpc_id = "${data.aws_vpc.vpc.id}" + vpc_id = data.aws_vpc.vpc.id ingress { from_port = 0 to_port = 65535 protocol = "tcp" - cidr_blocks = "${var.allowed_cidr_blocks}" + cidr_blocks = var.allowed_cidr_blocks } ingress { from_port = 0 to_port = 65535 protocol = "udp" - cidr_blocks = "${var.allowed_cidr_blocks}" + cidr_blocks = var.allowed_cidr_blocks } egress { @@ -102,12 +110,13 @@ resource "aws_security_group" "ecs" { cidr_blocks = ["0.0.0.0/0"] } - tags { + tags = { Name = "ecs-sg-${var.name}" } } # Make this a var that an get passed in? resource "aws_ecs_cluster" "cluster" { - name = "${var.name}" + name = var.name } + diff --git a/outputs.tf b/outputs.tf index f2d1493..abb88e9 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,38 +1,39 @@ output "cluster_id" { - value = "${aws_ecs_cluster.cluster.id}" + value = aws_ecs_cluster.cluster.id } output "cluster_name" { - value = "${aws_ecs_cluster.cluster.name}" + value = aws_ecs_cluster.cluster.name } output "instance_role_arn" { - value = "${aws_iam_role.ecs_role.arn}" + value = aws_iam_role.ecs_role.arn } output "instance_role_id" { - value = "${aws_iam_role.ecs_role.id}" + value = aws_iam_role.ecs_role.id } output "autoscaling_group" { value = { - id = "${aws_autoscaling_group.ecs.id}" - name = "${aws_autoscaling_group.ecs.name}" - arn = "${aws_autoscaling_group.ecs.arn}" + id = aws_autoscaling_group.ecs.id + name = aws_autoscaling_group.ecs.name + arn = aws_autoscaling_group.ecs.arn } } output "iam_role" { value = { - name = "${aws_iam_role.ecs_role.name}" - arn = "${aws_iam_role.ecs_role.arn}" + name = aws_iam_role.ecs_role.name + arn = aws_iam_role.ecs_role.arn } } output "security_group" { value = { - id = "${aws_security_group.ecs.id}" - name = "${aws_security_group.ecs.name}" - arn = "${aws_security_group.ecs.arn}" + id = aws_security_group.ecs.id + name = aws_security_group.ecs.name + arn = aws_security_group.ecs.arn } } + diff --git a/variables.tf b/variables.tf index 02a7c0d..dec152b 100644 --- a/variables.tf +++ b/variables.tf @@ -4,7 +4,7 @@ variable "additional_user_data_script" { variable "allowed_cidr_blocks" { default = ["0.0.0.0/0"] - type = "list" + type = list(string) description = "List of subnets to allow into the ECS Security Group. Defaults to ['0.0.0.0/0']" } @@ -56,8 +56,8 @@ variable "ebs_block_device" { } variable "extra_tags" { - type = "list" - default = [] + type = map(string) + default = {} } variable "heartbeat_timeout" { @@ -90,7 +90,7 @@ variable "key_name" { } variable "load_balancers" { - type = "list" + type = list(string) default = [] description = "A list of elastic load balancer names to add to the autoscaling group names. Only valid for classic load balancers." } @@ -129,7 +129,7 @@ variable "registrator_memory_reservation" { } variable "security_group_ids" { - type = "list" + type = list(string) description = "A list of Security group IDs to apply to the launch configuration" default = [] } @@ -145,7 +145,7 @@ variable "spot_bid_price" { } variable "subnet_id" { - type = "list" + type = list(string) description = "The AWS Subnet ID in which you want to delpoy your instances" } @@ -164,6 +164,7 @@ variable "vpc_id" { variable "enabled_metrics" { description = "A list of metrics to collect" - type = "list" + type = list(string) default = [] } + diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..ac97c6a --- /dev/null +++ b/versions.tf @@ -0,0 +1,4 @@ + +terraform { + required_version = ">= 0.12" +}