diff --git a/asg/files/instance-config.yml.tpl b/asg/files/instance-config.yml.tpl new file mode 100644 index 00000000..9799951a --- /dev/null +++ b/asg/files/instance-config.yml.tpl @@ -0,0 +1,2 @@ +#instance-config +${custom_script} \ No newline at end of file diff --git a/asg/main.tf b/asg/main.tf new file mode 100644 index 00000000..c5ef2472 --- /dev/null +++ b/asg/main.tf @@ -0,0 +1,292 @@ +/** + */ + +variable "name" { + description = "The cluster name, e.g cdn" + type = "string" +} + +variable "environment" { + description = "Environment tag, e.g prod" + type = "string" +} + +variable "vpc_id" { + description = "VPC ID" + type = "string" +} + +variable "image_id" { + description = "AMI Image ID" + type = "string" +} + +variable "subnet_ids" { + description = "list of subnet IDs" + type = "list" +} + +variable "key_name" { + description = "SSH key name to use" + type = "string" +} + +variable "ingress_cidr" { + description = "Comma separated list of ingress cidrs" + type = "string" +} + +variable "iam_instance_profile" { + description = "Instance profile ARN to use in the launch configuration" + type = "string" +} + +variable "region" { + description = "AWS Region" + type = "string" +} + +variable "availability_zones" { + description = "list of AZs" + type = "list" +} + +variable "instance_type" { + description = "The instance type to use, e.g t2.small" + type = "string" +} + +variable "instance_ebs_optimized" { + description = "When set to true the instance will be launched with EBS optimized turned on" + default = true +} + +variable "min_size" { + description = "Minimum instance count" + default = 3 +} + +variable "max_size" { + description = "Maxmimum instance count" + default = 100 +} + +variable "desired_capacity" { + description = "Desired instance count" + default = 3 +} + +variable "associate_public_ip_address" { + description = "Should created instances be publicly accessible (if the SG allows)" + default = false +} + +variable "custom_script" { + description = "Custom instance bootupt script" + default = "" + type = "string" +} + +variable "load_balancers" { + description = "ASG ELBs" + default = [] +} + +variable "health_check_grace_period" { + description = "Time (in seconds) after instance comes into service before checking health" + default = 300 +} + +variable "target_group_arns" { + description = "Application load balancer target group ARN(s) if any" + default = [] +} + +variable "termination_policies" { + description = "OldestInstance, NewestInstance, OldestLaunchConfiguration, ClosestToNextInstanceHour, Default" + default = ["OldestLaunchConfiguration", "Default"] +} + +variable "wait_for_capacity_timeout" { + description = "A maximum duration that Terraform should wait for ASG instances to be healthy before timing out" + default = "10m" +} + +resource "aws_security_group" "asg" { + name = "${var.name}-asg" + vpc_id = "${var.vpc_id}" + description = "Allows traffic from and to the EC2 instances of the ${var.name} ASG" + + ingress { + from_port = 0 + to_port = 0 + protocol = -1 + cidr_blocks = ["${split(",", var.ingress_cidr)}"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = -1 + cidr_blocks = ["0.0.0.0/0"] + } + + tags { + Name = "ASG (${var.name})" + Environment = "${var.environment}" + } + + lifecycle { + create_before_destroy = true + } +} + +data "template_file" "instance_config" { + template = "${file("${path.module}/files/instance-config.yml.tpl")}" + + vars { + custom_script = "${var.custom_script}" + } +} + +resource "aws_launch_configuration" "main" { + name_prefix = "${format("%s-", var.name)}" + + image_id = "${var.image_id}" + instance_type = "${var.instance_type}" + ebs_optimized = "${var.instance_ebs_optimized}" + iam_instance_profile = "${var.iam_instance_profile}" + key_name = "${var.key_name}" + security_groups = ["${aws_security_group.asg.id}"] + user_data = "${data.template_file.instance_config.rendered}" + associate_public_ip_address = "${var.associate_public_ip_address}" + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_autoscaling_group" "main" { + name = "${var.name}" + + availability_zones = ["${var.availability_zones}"] + vpc_zone_identifier = ["${var.subnet_ids}"] + launch_configuration = "${aws_launch_configuration.main.id}" + min_size = "${var.min_size}" + max_size = "${var.max_size}" + desired_capacity = "${var.desired_capacity}" + health_check_grace_period = "${var.health_check_grace_period}" + health_check_type = "ELB" + load_balancers = ["${var.load_balancers}"] + target_group_arns = ["${var.target_group_arns}"] + termination_policies = "${var.termination_policies}" + wait_for_capacity_timeout = "${var.wait_for_capacity_timeout}" + + tag { + key = "Name" + value = "${var.name}" + propagate_at_launch = true + } + + tag { + key = "Cluster" + value = "${var.name}" + propagate_at_launch = true + } + + tag { + key = "Environment" + value = "${var.environment}" + propagate_at_launch = true + } + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_autoscaling_policy" "scale_up" { + name = "${var.name}-scaleup" + scaling_adjustment = 1 + adjustment_type = "ChangeInCapacity" + cooldown = 300 + autoscaling_group_name = "${aws_autoscaling_group.main.name}" + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_autoscaling_policy" "scale_down" { + name = "${var.name}-scaledown" + scaling_adjustment = -1 + adjustment_type = "ChangeInCapacity" + cooldown = 300 + autoscaling_group_name = "${aws_autoscaling_group.main.name}" + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_cloudwatch_metric_alarm" "cpu_high" { + alarm_name = "${var.name}-cpureservation-high" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "2" + metric_name = "CPUUtilization" + namespace = "AWS/EC2" + period = "300" + statistic = "Maximum" + threshold = "60" + + dimensions { + AutoScalingGroupName = "${aws_autoscaling_group.main.name}" + } + + alarm_description = "Scale up if the cpu reservation is above 60% for 10 minutes" + alarm_actions = ["${aws_autoscaling_policy.scale_up.arn}"] + + lifecycle { + create_before_destroy = true + } + + # This is required to make cloudwatch alarms creation sequential, AWS doesn't + # support modifying alarms concurrently. + depends_on = ["aws_autoscaling_group.main"] +} + +resource "aws_cloudwatch_metric_alarm" "cpu_low" { + alarm_name = "${var.name}-cpureservation-low" + comparison_operator = "LessThanOrEqualToThreshold" + evaluation_periods = "2" + metric_name = "CPUUtilization" + namespace = "AWS/EC2" + period = "300" + statistic = "Maximum" + threshold = "10" + + dimensions { + AutoScalingGroupName = "${aws_autoscaling_group.main.name}" + } + + alarm_description = "Scale down if the cpu reservation is below 10% for 10 minutes" + alarm_actions = ["${aws_autoscaling_policy.scale_down.arn}"] + + lifecycle { + create_before_destroy = true + } + + # This is required to make cloudwatch alarms creation sequential, AWS doesn't + # support modifying alarms concurrently. + depends_on = ["aws_cloudwatch_metric_alarm.cpu_high"] +} + +// The asg name, e.g cdn +output "name" { + value = "${var.name}" +} + +// The asg security group ID. +output "security_group_id" { + value = "${aws_security_group.asg.id}" +} \ No newline at end of file diff --git a/bastion/main.tf b/bastion/main.tf index d5fe9a68..f6a10e4b 100644 --- a/bastion/main.tf +++ b/bastion/main.tf @@ -24,14 +24,17 @@ variable "instance_type" { default = "t2.micro" description = "Instance type, see a list at: https://aws.amazon.com/ec2/instance-types/" + type = "string" } variable "region" { description = "AWS Region, e.g us-west-2" + type = "string" } variable "security_groups" { - description = "a comma separated lists of security group IDs" + description = "List of security group IDs" + type = "list" } variable "vpc_id" { @@ -63,7 +66,7 @@ resource "aws_instance" "bastion" { instance_type = "${var.instance_type}" subnet_id = "${var.subnet_id}" key_name = "${var.key_name}" - vpc_security_group_ids = ["${split(",",var.security_groups)}"] + vpc_security_group_ids = ["${var.security_groups}"] monitoring = true user_data = "${file(format("%s/user_data.sh", path.module))}" diff --git a/dns/main.tf b/dns/main.tf index 2e3a7331..7c21a384 100644 --- a/dns/main.tf +++ b/dns/main.tf @@ -40,5 +40,5 @@ output "zone_id" { // A comma separated list of the zone name servers. output "name_servers" { - value = "${join(",",aws_route53_zone.main.name_servers)}" + value = ["${aws_route53_zone.main.name_servers}"] } diff --git a/ecs-cluster/main.tf b/ecs-cluster/main.tf index 4fae8565..e166ecb4 100644 --- a/ecs-cluster/main.tf +++ b/ecs-cluster/main.tf @@ -51,7 +51,8 @@ variable "key_name" { } variable "security_groups" { - description = "Comma separated list of security groups" + description = "List of security groups" + type = "list" } variable "iam_instance_profile" { @@ -135,7 +136,7 @@ resource "aws_security_group" "cluster" { from_port = 0 to_port = 0 protocol = -1 - security_groups = ["${split(",", var.security_groups)}"] + security_groups = ["${var.security_groups}"] } egress { diff --git a/elb/main.tf b/elb/main.tf index fd5063ad..5c4ecbe6 100644 --- a/elb/main.tf +++ b/elb/main.tf @@ -6,42 +6,78 @@ variable "name" { description = "ELB name, e.g cdn" + type = "string" } variable "subnet_ids" { - description = "Comma separated list of subnet IDs" + description = "List of subnet IDs" + type = "list" } variable "environment" { description = "Environment tag, e.g prod" + type = "string" } variable "port" { description = "Instance port" + type = "string" } variable "security_groups" { - description = "Comma separated list of security group IDs" + description = "List of security group IDs" + type = "list" } variable "dns_name" { description = "Route53 record name" + type = "string" } variable "healthcheck" { description = "Healthcheck path" + type = "string" +} + +variable "healthcheck_healthy_threshold" { + description = "Number of consecutive health check successes before declaring an EC2 instance healthy." + default = 2 +} + +variable "healthcheck_unhealthy_threshold" { + description = "Number of consecutive health check failures before declaring an EC2 instance unhealthy." + default = 2 +} + +variable "healthcheck_timeout" { + description = "Time to wait when receiving a response from the health check (2 sec 60 sec)." + default = 5 +} + +variable "healthcheck_interval" { + description = "Amount of time between health checks (5 sec 300 sec)" + default = 30 } variable "protocol" { description = "Protocol to use, HTTP or TCP" + type = "string" } variable "zone_id" { description = "Route53 zone ID to use for dns_name" + type = "string" } variable "log_bucket" { description = "S3 bucket name to write ELB logs into" + type = "string" +} + +# https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/config-idle-timeout.html?icmpid=docs_elb_console +variable "idle_timeout" { + description = "ELB idle connection timeout" + default = 30 } /** @@ -53,26 +89,26 @@ resource "aws_elb" "main" { internal = true cross_zone_load_balancing = true - subnets = ["${split(",", var.subnet_ids)}"] - security_groups = ["${split(",",var.security_groups)}"] + subnets = ["${var.subnet_ids}"] + security_groups = ["${var.security_groups}"] - idle_timeout = 30 + idle_timeout = "${var.idle_timeout}" connection_draining = true connection_draining_timeout = 15 listener { - lb_port = 80 + lb_port = "${var.port}" lb_protocol = "${var.protocol}" instance_port = "${var.port}" instance_protocol = "${var.protocol}" } health_check { - healthy_threshold = 2 - unhealthy_threshold = 2 - timeout = 5 + healthy_threshold = "${var.healthcheck_healthy_threshold}" + unhealthy_threshold = "${var.healthcheck_unhealthy_threshold}" + timeout = "${var.healthcheck_timeout}" target = "${var.protocol}:${var.port}${var.healthcheck}" - interval = 30 + interval = "${var.healthcheck_interval}" } access_logs { @@ -102,6 +138,11 @@ resource "aws_route53_record" "main" { * Outputs. */ +// Instance port +output "port" { + value = "${var.port}" +} + // The ELB name. output "name" { value = "${aws_elb.main.name}" @@ -126,3 +167,4 @@ output "fqdn" { output "zone_id" { value = "${aws_elb.main.zone_id}" } + diff --git a/iam-role/main.tf b/iam-role/main.tf index f5bd37c9..e5473193 100644 --- a/iam-role/main.tf +++ b/iam-role/main.tf @@ -6,10 +6,18 @@ variable "environment" { description = "The name of the environment for this stack" } -resource "aws_iam_role" "default_ecs_role" { - name = "ecs-role-${var.name}-${var.environment}" +variable "ecs_role_services" { + description = "" + default = "\"ec2.amazonaws.com\"" +} + +variable "aws_iam_role_policy_allow" { + description = "" + default = "\"autoscaling:*\",\"cloudwatch:*\"" +} - assume_role_policy = <