Skip to content
This repository has been archived by the owner on Jun 27, 2018. It is now read-only.

Commit

Permalink
Merge pull request #71 from TeliaSoneraNorge/ecs-fargate-module
Browse files Browse the repository at this point in the history
ECS Fargate module
  • Loading branch information
mikael-lindstrom authored Jun 11, 2018
2 parents f9d5414 + d9c8068 commit 939cb88
Show file tree
Hide file tree
Showing 7 changed files with 419 additions and 10 deletions.
2 changes: 2 additions & 0 deletions .ci/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ jobs:
<<: *test-module-params
directories: |
ecs/cluster
ecs/fargate
ecs/service
ecs/microservice
ecs/spotfleet
Expand Down Expand Up @@ -642,6 +643,7 @@ jobs:
<<: *test-module-params
directories: |
ecs/cluster
ecs/fargate
ecs/service
ecs/microservice
ecs/spotfleet
Expand Down
30 changes: 20 additions & 10 deletions ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ Set up an ECS cluster and register services with ease. The modules set up the fo
- CloudWatch log group for the ECS agent.
- IAM role/instance profile with appropriate privileges.

#### ecs/spotfleet
Cluster on a spotfleet - [detailed README](spotfleet/README.md)
- Ongoing Spotfleet request
- Amazon ECS Optimized AMI with ECS and SSM agents running.
- A security group for the cluster (with all egress and ingress from the specified load balancers).
- CloudWatch log group for the ECS agent.
- IAM role/instance profile with appropriate privileges.
#### ecs/fargate

- Fargate version of `ecs/service` which creates containers in a VPC
- Creates the task definition with an attached task role.
- Sets up the target group and security group for the service.
- Usable with either an ALB or a NLB.
- Sets up and enables logging for the task.
- Creates IAM roles for the ECS service.

#### ecs/microservice

- Wrapper for `ecs/service` which also...
- Assumes that a default listener has been created, and sets up a listener rule.

#### ecs/service

Expand All @@ -26,7 +32,11 @@ Cluster on a spotfleet - [detailed README](spotfleet/README.md)
- Sets up and enables logging for the task.
- Creates IAM roles for the ECS service.

#### ecs/microservice
#### ecs/spotfleet

- Wrapper for `ecs/service` which also...
- Assumes that a default listener has been created, and sets up a listener rule.
Cluster on a spotfleet - [detailed README](spotfleet/README.md)
- Ongoing Spotfleet request
- Amazon ECS Optimized AMI with ECS and SSM agents running.
- A security group for the cluster (with all egress and ingress from the specified load balancers).
- CloudWatch log group for the ECS agent.
- IAM role/instance profile with appropriate privileges.
65 changes: 65 additions & 0 deletions ecs/example/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,68 @@ data "aws_iam_policy_document" "privileges" {
]
}
}

# ----------------------------------------
# ecs/fargate: Create a service fargate
# ----------------------------------------
resource "aws_ecs_cluster" "fargate_cluster" {
name = "${var.prefix}-ecs-fargate-cluster"
}

module "fargate_alb" {
source = "github.com/teliasoneranorge/telia-terraform-modules//ec2/lb?ref=2018.06.04.1"

prefix = "${var.prefix}"
type = "application"
internal = "false"
vpc_id = "${module.vpc.vpc_id}"
subnet_ids = ["${module.vpc.public_subnet_ids}"]
tags = "${var.tags}"
}

resource "aws_lb_listener" "fargate" {
load_balancer_arn = "${module.fargate_alb.arn}"
port = "80"
protocol = "HTTP"

default_action {
target_group_arn = "${module.fargate.target_group_arn}"
type = "forward"
}
}

resource "aws_security_group_rule" "fargate_task_ingress_8080" {
security_group_id = "${module.fargate.service_sg_id}"
type = "ingress"
protocol = "tcp"
from_port = "8080"
to_port = "8080"
source_security_group_id = "${module.fargate_alb.security_group_id}"
}

resource "aws_security_group_rule" "fargate_alb_ingress_80" {
security_group_id = "${module.fargate_alb.security_group_id}"
type = "ingress"
protocol = "tcp"
from_port = "80"
to_port = "80"
cidr_blocks = ["0.0.0.0/0"]
}

module "fargate" {
source = "../fargate"

prefix = "${var.prefix}-fargate-app"
vpc_id = "${module.vpc.vpc_id}"
private_subnet_ids = "${module.vpc.private_subnet_ids}"
cluster_id = "${aws_ecs_cluster.fargate_cluster.id}"
task_definition_image = "crccheck/hello-world:latest"
container_port = "8080"
container_protocol = "HTTP"

health_check {
path = "/"
}

tags = "${var.tags}"
}
164 changes: 164 additions & 0 deletions ecs/fargate/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# ------------------------------------------------------------------------------
# AWS
# ------------------------------------------------------------------------------
data "aws_region" "current" {}

# ------------------------------------------------------------------------------
# Cloudwatch
# ------------------------------------------------------------------------------
resource "aws_cloudwatch_log_group" "main" {
name = "${var.prefix}"
retention_in_days = "${var.log_retention_in_days}"
tags = "${var.tags}"
}

# ------------------------------------------------------------------------------
# IAM
# ------------------------------------------------------------------------------
resource "aws_iam_role" "service" {
name = "${var.prefix}-service-role"
assume_role_policy = "${data.aws_iam_policy_document.service_assume.json}"
}

resource "aws_iam_role_policy" "service_permissions" {
name = "${var.prefix}-service-permissions"
role = "${aws_iam_role.service.id}"
policy = "${data.aws_iam_policy_document.service_permissions.json}"
}

resource "aws_iam_role" "task" {
name = "${var.prefix}-task-role"
assume_role_policy = "${data.aws_iam_policy_document.task_assume.json}"
}

resource "aws_iam_role_policy" "log_agent" {
name = "${var.prefix}-log-permissions"
role = "${aws_iam_role.task.id}"
policy = "${data.aws_iam_policy_document.task_permissions.json}"
}

# ------------------------------------------------------------------------------
# Security groups
# ------------------------------------------------------------------------------
resource "aws_security_group" "ecs_service" {
vpc_id = "${var.vpc_id}"
name = "${var.prefix}-ecs-service-sg"
description = "Fargate service security group"
tags = "${var.tags}"
}

resource "aws_security_group_rule" "ingress_service" {
security_group_id = "${aws_security_group.ecs_service.id}"
type = "ingress"
protocol = "icmp"
from_port = "8"
to_port = "0"
cidr_blocks = ["0.0.0.0/0"]
}

resource "aws_security_group_rule" "egress_service" {
security_group_id = "${aws_security_group.ecs_service.id}"
type = "egress"
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}

# ------------------------------------------------------------------------------
# LB Target group
# ------------------------------------------------------------------------------
resource "aws_lb_target_group" "task" {
vpc_id = "${var.vpc_id}"
protocol = "${var.task_container_protocol}"
port = "${var.task_container_port}"
target_type = "ip"
health_check = ["${var.health_check}"]

# NOTE: TF is unable to destroy a target group while a listener is attached,
# therefor we have to create a new one before destroying the old. This also means
# we have to let it have a random name, and then tag it with the desired name.
lifecycle {
create_before_destroy = true
}

tags = "${merge(var.tags, map("Name", "${var.prefix}-target-${var.task_container_port}"))}"
}

# ------------------------------------------------------------------------------
# ECS Task/Service
# ------------------------------------------------------------------------------
data "null_data_source" "task_environment" {
count = "${var.task_definition_environment_count}"

inputs = {
name = "${element(keys(var.task_definition_environment), count.index)}"
value = "${element(values(var.task_definition_environment), count.index)}"
}
}

resource "aws_ecs_task_definition" "task" {
family = "${var.prefix}"
execution_role_arn = "${aws_iam_role.task.arn}"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "${var.task_definition_cpu}"
memory = "${var.task_definition_ram}"

container_definitions = <<EOF
[{
"cpu":0,
"name": "${var.prefix}",
"image": "${var.task_definition_image}",
"essential": true,
"portMappings": [
{
"containerPort": ${var.task_container_port},
"hostPort": ${var.task_container_port},
"protocol":"tcp"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "${aws_cloudwatch_log_group.main.name}",
"awslogs-region": "${data.aws_region.current.name}",
"awslogs-stream-prefix": "container"
}
},
"environment": ${jsonencode(data.null_data_source.task_environment.*.outputs)}
}]
EOF
}

resource "aws_ecs_service" "service" {
depends_on = ["aws_iam_role_policy.service_permissions", "null_resource.lb_exists"]
name = "${var.prefix}"
cluster = "${var.cluster_id}"
task_definition = "${aws_ecs_task_definition.task.arn}"
desired_count = "${var.task_container_count}"
launch_type = "FARGATE"
deployment_minimum_healthy_percent = 50
deployment_maximum_percent = 200

network_configuration {
subnets = ["${var.private_subnet_ids}"]
security_groups = ["${aws_security_group.ecs_service.id}"]
}

load_balancer {
container_name = "${var.prefix}"
container_port = "${var.task_container_port}"
target_group_arn = "${aws_lb_target_group.task.arn}"
}
}

# HACK: The workaround used in ecs/service does not work for some reason in this module, this fixes the following error:
# "The target group with targetGroupArn arn:aws:elasticloadbalancing:... does not have an associated load balancer."
# see https://github.com/hashicorp/terraform/issues/12634.
# Service depends on this resources which prevents it from being created until the LB is ready
resource "null_resource" "lb_exists" {
triggers {
alb_name = "${var.lb_arn}"
}
}
30 changes: 30 additions & 0 deletions ecs/fargate/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# ------------------------------------------------------------------------------
# Output
# ------------------------------------------------------------------------------
output "service_arn" {
value = "${aws_ecs_service.service.id}"
}

output "target_group_arn" {
value = "${aws_lb_target_group.task.arn}"
}

output "service_role_arn" {
value = "${aws_iam_role.service.arn}"
}

output "service_role_name" {
value = "${aws_iam_role.service.name}"
}

output "task_role_arn" {
value = "${aws_iam_role.task.arn}"
}

output "task_role_name" {
value = "${aws_iam_role.task.name}"
}

output "service_sg_id" {
value = "${aws_security_group.ecs_service.id}"
}
61 changes: 61 additions & 0 deletions ecs/fargate/policies.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# ECS service assume policy
data "aws_iam_policy_document" "service_assume" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]

principals {
type = "Service"
identifiers = ["ecs.amazonaws.com"]
}
}
}

# ECS service permissions
data "aws_iam_policy_document" "service_permissions" {
# NOTE: ALB does not support resource level permissions :/
statement {
effect = "Allow"

actions = [
"elasticloadbalancing:Describe*",
"elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
"elasticloadbalancing:RegisterInstancesWithLoadBalancer",
"elasticloadbalancing:DeregisterTargets",
"elasticloadbalancing:RegisterTargets",
]

resources = [
"*",
]
}
}

# Task role assume policy
data "aws_iam_policy_document" "task_assume" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]

principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}

# Task logging privileges
data "aws_iam_policy_document" "task_permissions" {
statement {
effect = "Allow"

resources = [
"${aws_cloudwatch_log_group.main.arn}",
]

actions = [
"logs:CreateLogStream",
"logs:PutLogEvents",
]
}
}
Loading

0 comments on commit 939cb88

Please sign in to comment.