Skip to content

Commit 194fc7d

Browse files
authored
Initial implementation (#1)
* Initial implementation
1 parent fff1cef commit 194fc7d

File tree

10 files changed

+448
-2
lines changed

10 files changed

+448
-2
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ crash.log
55

66
# InteliJ IDE
77
.idea/
8+
# Visual Studio Code
9+
.vs/

.pre-commit-config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
repos:
3+
- repo: git://github.com/antonbabenko/pre-commit-terraform
4+
rev: v1.7.3
5+
hooks:
6+
- id: terraform_fmt
7+
- repo: git://github.com/pre-commit/pre-commit-hooks
8+
rev: v1.4.0
9+
hooks:
10+
- id: check-merge-conflict

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ test:
2727
@for d in $$(find . -type f -name '*.tf' -path "./examples/*" -not -path "**/.terraform/*" -exec dirname {} \; | sort -u); do \
2828
cd $$d; \
2929
terraform init -backend=false >> /dev/null; \
30-
terraform validate -check-variables=false; \
30+
terraform validate; \
3131
if [ $$? -eq 1 ]; then \
3232
echo "✗ terraform validate failed: $$d"; \
3333
exit 1; \

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1-
## Fargate ECS
1+
# AWS Fargate ECS Terraform Module
22

33
[![Build Status](https://travis-ci.com/telia-oss/terraform-aws-ecs-fargate.svg?branch=master)](https://travis-ci.com/telia-oss/terraform-aws-ecs-fargate)
4+
![](https://img.shields.io/maintenance/yes/2018.svg)
45

6+
Terraform module which creates Fargate ECS resources on AWS.
7+
8+
## Examples
9+
10+
* [Complete AWS Fargate ECS example](https://github.com/telia-oss/terraform-aws-ecs-fargate/examples/default/example.tf)
11+
12+
## Authors
13+
14+
Currently maintained by [these contributors](https://github.com/telia-oss/terraform-aws-ecs-fargate/graphs/contributors).
15+
16+
## License
17+
18+
MIT License. See LICENSE for full details.

examples/default/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## examples/default
2+
3+
Basic example which creates an Fargate ECS cluster with a default listener and hello-world service (in the default VPC).

examples/default/example.tf

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# ----------------------------------------
2+
# Create a ecs service using fargate
3+
# ----------------------------------------
4+
5+
provider "aws" {
6+
region = "eu-west-1"
7+
}
8+
9+
resource "aws_ecs_cluster" "cluster" {
10+
name = "example-ecs-cluster"
11+
}
12+
13+
data "aws_vpc" "main" {
14+
default = true
15+
}
16+
17+
data "aws_subnet_ids" "main" {
18+
vpc_id = "${data.aws_vpc.main.id}"
19+
}
20+
21+
module "fargate_alb" {
22+
source = "telia-oss/loadbalancer/aws"
23+
version = "0.1.0"
24+
25+
name_prefix = "example-ecs-cluster"
26+
type = "application"
27+
internal = "false"
28+
vpc_id = "${data.aws_vpc.main.id}"
29+
subnet_ids = ["${data.aws_subnet_ids.main.ids}"]
30+
31+
tags {
32+
environment = "test"
33+
terraform = "true"
34+
}
35+
}
36+
37+
resource "aws_lb_listener" "alb" {
38+
load_balancer_arn = "${module.fargate_alb.arn}"
39+
port = "80"
40+
protocol = "HTTP"
41+
42+
default_action {
43+
target_group_arn = "${module.fargate.target_group_arn}"
44+
type = "forward"
45+
}
46+
}
47+
48+
resource "aws_security_group_rule" "task_ingress_8000" {
49+
security_group_id = "${module.fargate.service_sg_id}"
50+
type = "ingress"
51+
protocol = "tcp"
52+
from_port = "8000"
53+
to_port = "8000"
54+
source_security_group_id = "${module.fargate_alb.security_group_id}"
55+
}
56+
57+
resource "aws_security_group_rule" "alb_ingress_80" {
58+
security_group_id = "${module.fargate_alb.security_group_id}"
59+
type = "ingress"
60+
protocol = "tcp"
61+
from_port = "80"
62+
to_port = "80"
63+
cidr_blocks = ["0.0.0.0/0"]
64+
ipv6_cidr_blocks = ["::/0"]
65+
}
66+
67+
module "fargate" {
68+
source = "../../"
69+
70+
name_prefix = "example-app"
71+
vpc_id = "${data.aws_vpc.main.id}"
72+
private_subnet_ids = "${data.aws_subnet_ids.main.ids}"
73+
cluster_id = "${aws_ecs_cluster.cluster.id}"
74+
task_container_image = "crccheck/hello-world:latest"
75+
76+
// public ip is needed for default vpc, default is false
77+
task_container_assign_public_ip = "true"
78+
79+
// port, default protocol is HTTP
80+
task_container_port = "8000"
81+
82+
health_check {
83+
port = "traffic-port"
84+
path = "/"
85+
}
86+
87+
tags {
88+
environment = "test"
89+
terraform = "true"
90+
}
91+
92+
lb_arn = "${module.fargate_alb.arn}"
93+
}

main.tf

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# ------------------------------------------------------------------------------
2+
# AWS
3+
# ------------------------------------------------------------------------------
4+
data "aws_region" "current" {}
5+
6+
# ------------------------------------------------------------------------------
7+
# Cloudwatch
8+
# ------------------------------------------------------------------------------
9+
resource "aws_cloudwatch_log_group" "main" {
10+
name = "${var.name_prefix}"
11+
retention_in_days = "${var.log_retention_in_days}"
12+
tags = "${var.tags}"
13+
}
14+
15+
# ------------------------------------------------------------------------------
16+
# IAM - Task execution role, needed to pull ECR images etc.
17+
# ------------------------------------------------------------------------------
18+
resource "aws_iam_role" "execution" {
19+
name = "${var.name_prefix}-task-execution-role"
20+
assume_role_policy = "${data.aws_iam_policy_document.task_assume.json}"
21+
}
22+
23+
resource "aws_iam_role_policy" "task_execution" {
24+
name = "${var.name_prefix}-task-execution"
25+
role = "${aws_iam_role.execution.id}"
26+
policy = "${data.aws_iam_policy_document.task_execution_permissions.json}"
27+
}
28+
29+
# ------------------------------------------------------------------------------
30+
# IAM - Task role, basic. Users of the module will append policies to this role
31+
# when they use the module. S3, Dynamo permissions etc etc.
32+
# ------------------------------------------------------------------------------
33+
resource "aws_iam_role" "task" {
34+
name = "${var.name_prefix}-task-role"
35+
assume_role_policy = "${data.aws_iam_policy_document.task_assume.json}"
36+
}
37+
38+
resource "aws_iam_role_policy" "log_agent" {
39+
name = "${var.name_prefix}-log-permissions"
40+
role = "${aws_iam_role.task.id}"
41+
policy = "${data.aws_iam_policy_document.task_permissions.json}"
42+
}
43+
44+
# ------------------------------------------------------------------------------
45+
# Security groups
46+
# ------------------------------------------------------------------------------
47+
resource "aws_security_group" "ecs_service" {
48+
vpc_id = "${var.vpc_id}"
49+
name = "${var.name_prefix}-ecs-service-sg"
50+
description = "Fargate service security group"
51+
tags = "${merge(var.tags, map("Name", "${var.name_prefix}-sg"))}"
52+
}
53+
54+
resource "aws_security_group_rule" "egress_service" {
55+
security_group_id = "${aws_security_group.ecs_service.id}"
56+
type = "egress"
57+
protocol = "-1"
58+
from_port = 0
59+
to_port = 0
60+
cidr_blocks = ["0.0.0.0/0"]
61+
ipv6_cidr_blocks = ["::/0"]
62+
}
63+
64+
# ------------------------------------------------------------------------------
65+
# LB Target group
66+
# ------------------------------------------------------------------------------
67+
resource "aws_lb_target_group" "task" {
68+
vpc_id = "${var.vpc_id}"
69+
protocol = "${var.task_container_protocol}"
70+
port = "${var.task_container_port}"
71+
target_type = "ip"
72+
health_check = ["${var.health_check}"]
73+
74+
# NOTE: TF is unable to destroy a target group while a listener is attached,
75+
# therefor we have to create a new one before destroying the old. This also means
76+
# we have to let it have a random name, and then tag it with the desired name.
77+
lifecycle {
78+
create_before_destroy = true
79+
}
80+
81+
tags = "${merge(var.tags, map("Name", "${var.name_prefix}-target-${var.task_container_port}"))}"
82+
}
83+
84+
# ------------------------------------------------------------------------------
85+
# ECS Task/Service
86+
# ------------------------------------------------------------------------------
87+
data "null_data_source" "task_environment" {
88+
count = "${var.task_container_environment_count}"
89+
90+
inputs = {
91+
name = "${element(keys(var.task_container_environment), count.index)}"
92+
value = "${element(values(var.task_container_environment), count.index)}"
93+
}
94+
}
95+
96+
resource "aws_ecs_task_definition" "task" {
97+
family = "${var.name_prefix}"
98+
execution_role_arn = "${aws_iam_role.execution.arn}"
99+
network_mode = "awsvpc"
100+
requires_compatibilities = ["FARGATE"]
101+
cpu = "${var.task_definition_cpu}"
102+
memory = "${var.task_definition_memory}"
103+
task_role_arn = "${aws_iam_role.task.arn}"
104+
105+
container_definitions = <<EOF
106+
[{
107+
"name": "${var.name_prefix}",
108+
"image": "${var.task_container_image}",
109+
"essential": true,
110+
"portMappings": [
111+
{
112+
"containerPort": ${var.task_container_port},
113+
"hostPort": ${var.task_container_port},
114+
"protocol":"tcp"
115+
}
116+
],
117+
"logConfiguration": {
118+
"logDriver": "awslogs",
119+
"options": {
120+
"awslogs-group": "${aws_cloudwatch_log_group.main.name}",
121+
"awslogs-region": "${data.aws_region.current.name}",
122+
"awslogs-stream-prefix": "container"
123+
}
124+
},
125+
"command": ${jsonencode(var.task_container_command)},
126+
"environment": ${jsonencode(data.null_data_source.task_environment.*.outputs)}
127+
}]
128+
EOF
129+
}
130+
131+
resource "aws_ecs_service" "service" {
132+
depends_on = ["null_resource.lb_exists"]
133+
name = "${var.name_prefix}"
134+
cluster = "${var.cluster_id}"
135+
task_definition = "${aws_ecs_task_definition.task.arn}"
136+
desired_count = "${var.desired_count}"
137+
launch_type = "FARGATE"
138+
deployment_minimum_healthy_percent = 50
139+
deployment_maximum_percent = 200
140+
141+
network_configuration {
142+
subnets = ["${var.private_subnet_ids}"]
143+
security_groups = ["${aws_security_group.ecs_service.id}"]
144+
assign_public_ip = "${var.task_container_assign_public_ip}"
145+
}
146+
147+
load_balancer {
148+
container_name = "${var.name_prefix}"
149+
container_port = "${var.task_container_port}"
150+
target_group_arn = "${aws_lb_target_group.task.arn}"
151+
}
152+
}
153+
154+
# HACK: The workaround used in ecs/service does not work for some reason in this module, this fixes the following error:
155+
# "The target group with targetGroupArn arn:aws:elasticloadbalancing:... does not have an associated load balancer."
156+
# see https://github.com/hashicorp/terraform/issues/12634.
157+
# Service depends on this resources which prevents it from being created until the LB is ready
158+
resource "null_resource" "lb_exists" {
159+
triggers {
160+
alb_name = "${var.lb_arn}"
161+
}
162+
}

outputs.tf

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# ------------------------------------------------------------------------------
2+
# Output
3+
# ------------------------------------------------------------------------------
4+
output "service_arn" {
5+
description = "The Amazon Resource Name (ARN) that identifies the service."
6+
value = "${aws_ecs_service.service.id}"
7+
}
8+
9+
output "target_group_arn" {
10+
description = "The ARN of the Target Group."
11+
value = "${aws_lb_target_group.task.arn}"
12+
}
13+
14+
output "task_role_arn" {
15+
description = "The Amazon Resource Name (ARN) specifying the service role."
16+
value = "${aws_iam_role.task.arn}"
17+
}
18+
19+
output "task_role_name" {
20+
description = "The name of the service role."
21+
value = "${aws_iam_role.task.name}"
22+
}
23+
24+
output "service_sg_id" {
25+
description = "The Amazon Resource Name (ARN) that identifies the service security group."
26+
value = "${aws_security_group.ecs_service.id}"
27+
}

policies.tf

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Task role assume policy
2+
data "aws_iam_policy_document" "task_assume" {
3+
statement {
4+
effect = "Allow"
5+
actions = ["sts:AssumeRole"]
6+
7+
principals {
8+
type = "Service"
9+
identifiers = ["ecs-tasks.amazonaws.com"]
10+
}
11+
}
12+
}
13+
14+
# Task logging privileges
15+
data "aws_iam_policy_document" "task_permissions" {
16+
statement {
17+
effect = "Allow"
18+
19+
resources = [
20+
"${aws_cloudwatch_log_group.main.arn}",
21+
]
22+
23+
actions = [
24+
"logs:CreateLogStream",
25+
"logs:PutLogEvents",
26+
]
27+
}
28+
}
29+
30+
# Task ecr privileges
31+
data "aws_iam_policy_document" "task_execution_permissions" {
32+
statement {
33+
effect = "Allow"
34+
35+
resources = [
36+
"*",
37+
]
38+
39+
actions = [
40+
"ecr:GetAuthorizationToken",
41+
"ecr:BatchCheckLayerAvailability",
42+
"ecr:GetDownloadUrlForLayer",
43+
"ecr:BatchGetImage",
44+
"logs:CreateLogStream",
45+
"logs:PutLogEvents",
46+
]
47+
}
48+
}

0 commit comments

Comments
 (0)