ECS Module
Last updated
Last updated
In here I'm going to create a these modules and consume those to creaet the end solution with Terraform.
The project structure looks like this,
|-- modules
|-- alb-rule
|-- rule.tf
|-- alb
|-- alb.tf
|-- output.tf
|-- securitygroups.tf
|-- vars.tf
|-- ecs-cluster
|-- templates
|-- ecs_init.tpl
|-- cloudwatch.tf
|-- ecs.tf
|-- iam.tf
|-- output.tf
|-- securitygroups.tf
|-- vars.tf
|-- ecs-service
|-- alb.tf
|-- ecs-service.json
|-- ecs-service.tf
|-- output.tf
|-- vars.tf
|-- dev-env
|-- ecr-login.sh
|-- ecs.sh
|-- key.tf
|-- provider.tf
|-- securitygroup.tf
|-- vars.tf
|-- vpc.tf
modules/alb-rule/rule.tf
:
variable "LISTENER_ARN" {}
variable "PRIORITY" {}
variable "TARGET_GROUP_ARN" {}
variable "CONDITION_FIELD" {}
variable "CONDITION_VALUES" {
type = "list"
}
resource "aws_lb_listener_rule" "alb_rule" {
listener_arn = "${var.LISTENER_ARN}"
priority = "${var.PRIORITY}"
action {
type = "forward"
target_group_arn = "${var.TARGET_GROUP_ARN}"
}
condition {
field = "${var.CONDITION_FIELD}"
values = ["${var.CONDITION_VALUES}"]
}
}
modules/alb/alb.tf
:
#
# ECS ALB
#
# alb main definition
resource "aws_alb" "alb" {
name = "${var.ALB_NAME}"
internal = "${var.INTERNAL}"
security_groups = ["${aws_security_group.alb.id}"]
subnets = ["${split(",", var.VPC_SUBNETS)}"]
enable_deletion_protection = false
}
# certificate
data "aws_acm_certificate" "certificate" {
domain = "${var.DOMAIN}"
statuses = ["ISSUED", "PENDING_VALIDATION"]
}
# alb listener (https)
resource "aws_alb_listener" "alb-https" {
load_balancer_arn = "${aws_alb.alb.arn}"
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-2016-08"
certificate_arn = "${data.aws_acm_certificate.certificate.arn}"
default_action {
target_group_arn = "${var.DEFAULT_TARGET_ARN}"
type = "forward"
}
}
# alb listener (http)
resource "aws_alb_listener" "alb-http" {
load_balancer_arn = "${aws_alb.alb.arn}"
port = "80"
protocol = "HTTP"
default_action {
target_group_arn = "${var.DEFAULT_TARGET_ARN}"
type = "forward"
}
}
modules/alb/output.tf
:
output "dns_name" {
value = "${aws_alb.alb.dns_name}"
}
output "alb_arn" {
value = "${aws_alb.alb.arn}"
}
output "zone_id" {
value = "${aws_alb.alb.zone_id}"
}
output "http_listener_arn" {
value = "${aws_alb_listener.alb-http.arn}"
}
output "https_listener_arn" {
value = "${aws_alb_listener.alb-https.arn}"
}
modules/alb/securitygroups.tf
:
resource "aws_security_group" "alb" {
name = "${var.ALB_NAME}"
vpc_id = "${var.VPC_ID}"
description = "${var.ALB_NAME}"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group_rule" "cluster-allow-alb" {
security_group_id = "${var.ECS_SG}"
type = "ingress"
from_port = 32768
to_port = 61000
protocol = "tcp"
source_security_group_id = "${aws_security_group.alb.id}"
}
modules/alb/vars.tf
:
variable "ALB_NAME" {}
variable "INTERNAL" {}
variable "VPC_ID" {}
variable "VPC_SUBNETS" {}
variable "DOMAIN" {}
variable "DEFAULT_TARGET_ARN" {}
variable "ECS_SG" {
default = ""
}
modules/ecs-cluster/templates/ecs_init.tpl
:
#!/bin/bash
echo 'ECS_CLUSTER=${CLUSTER_NAME}' > /etc/ecs/ecs.config
start ecs
modules/ecs-cluster/cloudwatch.tf
:
#
# Cloudwatch logs
#
resource "aws_cloudwatch_log_group" "cluster" {
name = "${var.LOG_GROUP}"
}
modules/ecs-cluster/ecs.tf
#
# ECS ami
#
data "aws_ami" "ecs" {
most_recent = true
filter {
name = "name"
values = ["amzn-ami-*-amazon-ecs-optimized"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["591542846629"] # AWS
}
#
# ECS cluster
#
resource "aws_ecs_cluster" "cluster" {
name = "${var.CLUSTER_NAME}"
}
data "template_file" "ecs_init" {
template = "${file("${path.module}/templates/ecs_init.tpl")}"
vars {
CLUSTER_NAME = "${var.CLUSTER_NAME}"
}
}
#
# launchconfig
#
resource "aws_launch_configuration" "cluster" {
name_prefix = "ecs-${var.CLUSTER_NAME}-launchconfig"
image_id = "${data.aws_ami.ecs.id}"
instance_type = "${var.INSTANCE_TYPE}"
key_name = "${var.SSH_KEY_NAME}"
iam_instance_profile = "${aws_iam_instance_profile.cluster-ec2-role.id}"
security_groups = ["${aws_security_group.cluster.id}"]
user_data = "${data.template_file.ecs_init.rendered}"
lifecycle {
create_before_destroy = true
}
}
#
# autoscaling
#
resource "aws_autoscaling_group" "cluster" {
name = "ecs-${var.CLUSTER_NAME}-autoscaling"
vpc_zone_identifier = ["${split(",", var.VPC_SUBNETS)}"]
launch_configuration = "${aws_launch_configuration.cluster.name}"
termination_policies = ["${split(",", var.ECS_TERMINATION_POLICIES)}"]
min_size = "${var.ECS_MINSIZE}"
max_size = "${var.ECS_MAXSIZE}"
desired_capacity = "${var.ECS_DESIRED_CAPACITY}"
tag {
key = "Name"
value = "${var.CLUSTER_NAME}-ecs"
propagate_at_launch = true
}
}
modules/ecs-cluster/iam.tf
:
#
# ECS service role
#
resource "aws_iam_role" "cluster-service-role" {
name = "ecs-service-role-${var.CLUSTER_NAME}"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ecs.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_role_policy" "cluster-service-role" {
name = "${var.CLUSTER_NAME}-policy"
role = "${aws_iam_role.cluster-service-role.name}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:AuthorizeSecurityGroupIngress",
"ec2:Describe*",
"elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
"elasticloadbalancing:DeregisterTargets",
"elasticloadbalancing:Describe*",
"elasticloadbalancing:RegisterInstancesWithLoadBalancer",
"elasticloadbalancing:RegisterTargets"
],
"Resource": "*"
}
]
}
EOF
}
#
# IAM EC2 role
#
resource "aws_iam_role" "cluster-ec2-role" {
name = "ecs-${var.CLUSTER_NAME}-ec2-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_instance_profile" "cluster-ec2-role" {
name = "ecs-${var.CLUSTER_NAME}-ec2-role"
role = "${aws_iam_role.cluster-ec2-role.name}"
}
resource "aws_iam_role_policy" "cluster-ec2-role" {
name = "cluster-ec2-role-policy"
role = "${aws_iam_role.cluster-ec2-role.id}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecs:CreateCluster",
"ecs:DeregisterContainerInstance",
"ecs:DiscoverPollEndpoint",
"ecs:Poll",
"ecs:RegisterContainerInstance",
"ecs:StartTelemetrySession",
"ecs:Submit*",
"ecs:StartTask",
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"logs:*"
],
"Resource": [
"arn:aws:logs:${var.AWS_REGION}:${var.AWS_ACCOUNT_ID}:log-group:${var.LOG_GROUP}:*"
]
}
]
}
EOF
}
modules/ecs-cluster/output.tf
:
output "cluster_arn" {
value = "${aws_ecs_cluster.cluster.id}"
}
output "service_role_arn" {
value = "${aws_iam_role.cluster-service-role.arn}"
}
output "cluster_sg" {
value = "${aws_security_group.cluster.id}"
}
modules/ecs-cluster/securitygroups.tf
:
resource "aws_security_group" "cluster" {
name = "${var.CLUSTER_NAME}"
vpc_id = "${var.VPC_ID}"
description = "${var.CLUSTER_NAME}"
}
resource "aws_security_group_rule" "cluster-allow-ssh" {
count = "${ var.ENABLE_SSH ? 1 : 0}"
security_group_id = "${aws_security_group.cluster.id}"
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
source_security_group_id = "${var.SSH_SG}"
}
resource "aws_security_group_rule" "cluster-egress" {
security_group_id = "${aws_security_group.cluster.id}"
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
modules/ecs-cluster/vars.tf
:
variable "AWS_ACCOUNT_ID" {}
variable "AWS_REGION" {}
variable "LOG_GROUP" {}
variable "VPC_ID" {}
variable "CLUSTER_NAME" {}
variable "INSTANCE_TYPE" {}
variable "SSH_KEY_NAME" {}
variable "VPC_SUBNETS" {}
variable "ECS_TERMINATION_POLICIES" {
default = "OldestLaunchConfiguration,Default"
}
variable "ECS_MINSIZE" {
default = 1
}
variable "ECS_MAXSIZE" {
default = 1
}
variable "ECS_DESIRED_CAPACITY" {
default = 1
}
variable "ENABLE_SSH" {
default = false
}
variable "SSH_SG" {
default = ""
}
modules/ecs-service/alb.tf
:
#
# target
#
resource "aws_alb_target_group" "ecs-service" {
name = "${var.APPLICATION_NAME}-${substr(md5(format("%s%s%s", var.APPLICATION_PORT, var.DEREGISTRATION_DELAY, var.HEALTHCHECK_MATCHER)), 0, 12)}"
port = "${var.APPLICATION_PORT}"
protocol = "HTTP"
vpc_id = "${var.VPC_ID}"
deregistration_delay = "${var.DEREGISTRATION_DELAY}"
health_check {
healthy_threshold = 3
unhealthy_threshold = 3
protocol = "HTTP"
path = "/"
interval = 60
matcher = "${var.HEALTHCHECK_MATCHER}"
}
}
modules/ecs-service/ecs-service.json
:
[
{
"name": "${APPLICATION_NAME}",
"image": "${ECR_URL}:${APPLICATION_VERSION}",
"cpu": ${CPU_RESERVATION},
"memoryReservation": ${MEMORY_RESERVATION},
"essential": true,
"mountPoints": [],
"portMappings" : [
{
"containerPort": ${APPLICATION_PORT},
"hostPort": 0
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "${LOG_GROUP}",
"awslogs-region": "${AWS_REGION}",
"awslogs-stream-prefix": "${APPLICATION_NAME}"
}
}
}
]
modules/ecs-service/ecs-service.tf
:
#
# ECR
#
resource "aws_ecr_repository" "ecs-service" {
name = "${var.APPLICATION_NAME}"
}
#
# get latest active revision
#
data "aws_ecs_task_definition" "ecs-service" {
task_definition = "${aws_ecs_task_definition.ecs-service-taskdef.family}"
depends_on = ["aws_ecs_task_definition.ecs-service-taskdef"]
}
#
# task definition template
#
data "template_file" "ecs-service" {
template = "${file("${path.module}/ecs-service.json")}"
vars {
APPLICATION_NAME = "${var.APPLICATION_NAME}"
APPLICATION_PORT = "${var.APPLICATION_PORT}"
APPLICATION_VERSION = "${var.APPLICATION_VERSION}"
ECR_URL = "${aws_ecr_repository.ecs-service.repository_url}"
AWS_REGION = "${var.AWS_REGION}"
CPU_RESERVATION = "${var.CPU_RESERVATION}"
MEMORY_RESERVATION = "${var.MEMORY_RESERVATION}"
LOG_GROUP = "${var.LOG_GROUP}"
}
}
#
# task definition
#
resource "aws_ecs_task_definition" "ecs-service-taskdef" {
family = "${var.APPLICATION_NAME}"
container_definitions = "${data.template_file.ecs-service.rendered}"
task_role_arn = "${var.TASK_ROLE_ARN}"
}
#
# ecs service
#
resource "aws_ecs_service" "ecs-service" {
name = "${var.APPLICATION_NAME}"
cluster = "${var.CLUSTER_ARN}"
task_definition = "${aws_ecs_task_definition.ecs-service-taskdef.family}:${max("${aws_ecs_task_definition.ecs-service-taskdef.revision}", "${data.aws_ecs_task_definition.ecs-service.revision}")}"
iam_role = "${var.SERVICE_ROLE_ARN}"
desired_count = "${var.DESIRED_COUNT}"
deployment_minimum_healthy_percent = "${var.DEPLOYMENT_MINIMUM_HEALTHY_PERCENT}"
deployment_maximum_percent = "${var.DEPLOYMENT_MAXIMUM_PERCENT}"
load_balancer {
target_group_arn = "${aws_alb_target_group.ecs-service.id}"
container_name = "${var.APPLICATION_NAME}"
container_port = "${var.APPLICATION_PORT}"
}
depends_on = ["null_resource.alb_exists"]
}
resource "null_resource" "alb_exists" {
triggers {
alb_name = "${var.ALB_ARN}"
}
}
modules/ecs-service/output.tf
:
output "target_group_arn" {
value = "${aws_alb_target_group.ecs-service.arn}"
}
modules/ecs-service/vars.tf
:
variable "VPC_ID" {}
variable "AWS_REGION" {}
variable "APPLICATION_NAME" {}
variable "APPLICATION_PORT" {}
variable "APPLICATION_VERSION" {}
variable "CLUSTER_ARN" {}
variable "SERVICE_ROLE_ARN" {}
variable "DESIRED_COUNT" {}
variable "DEPLOYMENT_MINIMUM_HEALTHY_PERCENT" {
default = 100
}
variable "DEPLOYMENT_MAXIMUM_PERCENT" {
default = 200
}
variable "DEREGISTRATION_DELAY" {
default = 30
}
variable "HEALTHCHECK_MATCHER" {
default = "200"
}
variable "CPU_RESERVATION" {}
variable "MEMORY_RESERVATION" {}
variable "LOG_GROUP" {}
variable "TASK_ROLE_ARN" {
default = ""
}
variable "ALB_ARN" {}