Browse Source

Functional non parametrized ecs api template

main
gabriel becker 1 year ago
parent
commit
62c8acb0e8
  1. 2
      .gitignore
  2. 6
      README.md
  3. 40
      aws_fargate/alb.tf
  4. 61
      aws_fargate/ecs.tf
  5. 21
      aws_fargate/iam.tf
  6. 39
      aws_fargate/main.tf
  7. 32
      aws_fargate/network.tf
  8. 4
      aws_fargate/outputs.tf
  9. 27
      aws_fargate/variables.tf
  10. 38
      aws_fargate_2/alb.tf
  11. 13
      aws_fargate_2/config.tf
  12. 39
      aws_fargate_2/ecs.tf
  13. 24
      aws_fargate_2/iam.tf
  14. 132
      aws_fargate_2/network.tf
  15. 23
      aws_fargate_2/service.tf
  16. 44
      aws_fargate_2/variables.tf

2
.gitignore vendored

@ -1,3 +1,3 @@
.terraform .terraform
.terraform* .terraform*
*.tfstate* *.tfstate

6
README.md

@ -4,5 +4,7 @@ Provisioning scripts for personal learning.
References References
- [gruntwork](https://blog.gruntwork.io/an-introduction-to-terraform-f17df9c6d180) - [gruntwork](https://blog.gruntwork.io/an-introduction-to-terraform-f17df9c6d180)
- https://section411.com/2019/07/hello-world/ - [section41 blog post](https://section411.com/2019/07/hello-world/) tutorial on aws fargate with terraform
- https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition - aws ecs task definition [[terraform]] [documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition)
- [dwmkerr](https://dwmkerr.com/dynamic-and-configurable-availability-zones-in-terraform/) blog tutorial on how to use terraform interpotaion to create dynamic resources given an array of parameters
- [Setup ECS Fargate and CloudFront with Terraform](https://awstip.com/ecs-fargate-with-terraform-9cd755d46039) tutorial that works but I can't figure how its connected to all subents

40
aws_fargate/alb.tf

@ -0,0 +1,40 @@
resource "aws_lb_target_group" "api_lb_target" {
name = "my-api"
port = 3000
protocol = "HTTP"
target_type = "ip"
vpc_id = aws_vpc.app_vpc.id
health_check {
enabled = true
path = "/health"
}
depends_on = [aws_alb.api_lb]
}
resource "aws_alb" "api_lb" {
name = "${var.project}-api-lb"
internal = false
load_balancer_type = "application"
subnets = [for s in aws_subnet.public_subnet : s.id]
security_groups = [
aws_security_group.http.id,
aws_security_group.https.id,
aws_security_group.egress_all.id,
aws_security_group.ingress_api.id,
]
depends_on = [aws_internet_gateway.igw]
}
resource "aws_alb_listener" "api_http_listener" {
load_balancer_arn = aws_alb.api_lb.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.api_lb_target.arn
}
}
output "alb_url" {
value = "http://${aws_alb.api_lb.dns_name}"
}

61
aws_fargate/ecs.tf

@ -0,0 +1,61 @@
locals {
api_name = "${var.project}-api"
}
resource "aws_ecs_cluster" "my_cluster" {
name = "my_cluster"
}
resource "aws_ecs_service" "api_ecs" {
name = local.api_name
task_definition = aws_ecs_task_definition.api_task.arn
cluster = aws_ecs_cluster.my_cluster.id
launch_type = "FARGATE"
load_balancer {
target_group_arn = aws_lb_target_group.api_lb_target.arn
container_name = local.api_name
container_port = "3000"
}
desired_count = 1
network_configuration {
assign_public_ip = false
security_groups = [
aws_security_group.egress_all.id,
aws_security_group.ingress_api.id,
]
subnets = [for s in aws_subnet.private_subnet : s.id]
}
}
resource "aws_ecs_task_definition" "api_task" {
family = local.api_name
execution_role_arn = aws_iam_role.api_exec_role.arn
cpu = 256
memory = 512
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
container_definitions = jsonencode([
{
name = local.api_name,
image = var.container_image,
portMappings = [
{
containerPort = 3000
}
],
logConfiguration = {
logDriver = "awslogs",
options = {
awslogs-region = var.region,
awslogs-group = "/ecs/${local.api_name}",
awslogs-stream-prefix = "ecs"
}
}
}
])
}
resource "aws_cloudwatch_log_group" "log_group" {
name = "/ecs/${local.api_name}"
}

21
aws_fargate/iam.tf

@ -1,24 +1,20 @@
resource "aws_iam_role" "api_exec_role" { resource "aws_iam_role" "api_exec_role" {
name = "${var.project}-exec-role" name = "${var.project}-exec-role"
assume_role_policy = data.aws_iam_policy_document.api_exec_assume_role.json assume_role_policy = data.aws_iam_policy_document.api_exec_assume_role_policy_statement.json
} }
data "aws_iam_policy_document" "api_exec_assume_role" { data "aws_iam_policy_document" "api_exec_assume_role_policy_statement" {
statement { statement {
actions = ["sts:AssumeRole"] actions = ["sts:AssumeRole"]
principals { principals {
type = "Service" type = "Service"
identifiers = ["ecs-task.amazonaws.com"] identifiers = ["ecs-tasks.amazonaws.com"]
} }
} }
} }
resource "aws_iam_role_policy_attachment" "ecs_exec_iam_attach_assume_role" {
role = aws_iam_role.api_exec_role.name
policy_arn = data.aws_iam_policy_document.api_exec_assume_role
}
data "aws_iam_policy_document" "ecs_exec_role" { data "aws_iam_policy_document" "ecs_exec_policy_statement" {
statement { statement {
effect = "Allow" effect = "Allow"
actions = [ actions = [
@ -29,11 +25,16 @@ data "aws_iam_policy_document" "ecs_exec_role" {
"logs:CreateLogStream", "logs:CreateLogStream",
"logs:PutLogEvents" "logs:PutLogEvents"
] ]
resources = ["*${var.project}*"] resources = ["*"]
} }
} }
resource "aws_iam_policy" "ecs_exec_policy" {
name = "${var.project}-ecs_exec_policy"
policy = data.aws_iam_policy_document.ecs_exec_policy_statement.json
}
resource "aws_iam_role_policy_attachment" "ecs_exec_iam_attach_rules" { resource "aws_iam_role_policy_attachment" "ecs_exec_iam_attach_rules" {
role = aws_iam_role.api_exec_role.name role = aws_iam_role.api_exec_role.name
policy_arn = data.aws_iam_policy_document.ecs_exec_role.json policy_arn = aws_iam_policy.ecs_exec_policy.arn
} }

39
aws_fargate/main.tf

@ -1,39 +0,0 @@
resource "aws_ecs_service" "api_ecs" {
name = "${var.project}-api"
task_definition = aws_ecs_task_definition.api_task.arn
launch_type = "FARGATE"
}
resource "aws_ecs_task_definition" "api_task" {
family = "${var.project}-api"
cpu = 256
memory = 512
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
execution_role_arn = aws_iam_role.api_exec_role.arn
container_definitions = jsonencode([
{
name = "${var.project}-api",
image = var.container_image,
portMappings = [
{
containerPort = 3000
}
],
logConfiguration = {
logDriver = "awslogs",
options = {
awslogs-region = var.region,
awslogs-group = "/ecs/${var.project}-api",
awslogs-stream-prefix = "ecs"
}
}
}
])
}
resource "aws_cloudwatch_log_group" "log_group" {
name = "/ecs/${var.project}-api"
}

32
aws_fargate/network.tf

@ -3,24 +3,24 @@ resource "aws_vpc" "app_vpc" {
} }
resource "aws_subnet" "public_subnet" { resource "aws_subnet" "public_subnet" {
count = length(var.subnets) count = length(var.public_subnets)
vpc_id = aws_vpc.app_vpc.id vpc_id = aws_vpc.app_vpc.id
availability_zone = element(keys(var.subnets), count.index) availability_zone = element(keys(var.public_subnets), count.index)
cidr_block = "10.0.${count.index}.0/25" cidr_block = element(values(var.public_subnets), count.index)
tags = { tags = {
"Name" = "public | ${element(keys(var.subnets), count.index)}" "Name" = "public | ${element(keys(var.public_subnets), count.index)}"
} }
} }
resource "aws_subnet" "private_subnet" { resource "aws_subnet" "private_subnet" {
count = length(var.subnets) count = length(var.private_subnets)
vpc_id = aws_vpc.app_vpc.id vpc_id = aws_vpc.app_vpc.id
availability_zone = element(keys(var.subnets), count.index) availability_zone = element(keys(var.private_subnets), count.index)
cidr_block = "10.0.${count.index}.128/25" cidr_block = element(values(var.private_subnets), count.index)
tags = { tags = {
"Name" = "private | ${element(keys(var.subnets), count.index)}" "Name" = "private | ${element(keys(var.private_subnets), count.index)}"
} }
} }
@ -39,16 +39,12 @@ resource "aws_route_table" "private" {
} }
resource "aws_route_table_association" "public_subnet" { resource "aws_route_table_association" "public_subnet" {
count = length(var.subnets) subnet_id = aws_subnet.public_subnet[0].id
subnet_id = element(aws_subnet.public_subnet.*.id, count.index)
route_table_id = aws_route_table.public.id route_table_id = aws_route_table.public.id
} }
resource "aws_route_table_association" "private_subnet" { resource "aws_route_table_association" "private_subnet" {
count = length(var.subnets) subnet_id = aws_subnet.private_subnet[0].id
subnet_id = element(aws_subnet.private_subnet.*.id, count.index)
route_table_id = aws_route_table.private.id route_table_id = aws_route_table.private.id
} }
@ -61,20 +57,18 @@ resource "aws_internet_gateway" "igw" {
} }
resource "aws_nat_gateway" "ngw" { resource "aws_nat_gateway" "ngw" {
count = length(var.subnets) subnet_id = aws_subnet.private_subnet[0].id
subnet_id = element(aws_subnet.private_subnet.*.id, count.index)
allocation_id = aws_eip.nat.id allocation_id = aws_eip.nat.id
depends_on = [aws_internet_gateway.igw] depends_on = [aws_internet_gateway.igw]
} }
resource "aws_route" "public_igw" { resource "aws_route" "igw" {
route_table_id = aws_route_table.public.id route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0" destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id gateway_id = aws_internet_gateway.igw.id
} }
resource "aws_route" "private_ngw" { resource "aws_route" "ngw" {
route_table_id = aws_route_table.private.id route_table_id = aws_route_table.private.id
destination_cidr_block = "0.0.0.0/0" destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.ngw.id nat_gateway_id = aws_nat_gateway.ngw.id

4
aws_fargate/outputs.tf

@ -0,0 +1,4 @@
# output "ecs-ip" {
# description = "ecs-ip"
# value = [for s in aws_eip.nat.map : s.public_ip]
# }

27
aws_fargate/variables.tf

@ -1,9 +1,9 @@
variable "region" { variable "region" {
default = "us-east-1" default = "ap-southeast-2"
} }
variable "profile" { variable "profile" {
default = "default" default = "superuser"
} }
variable "project" { variable "project" {
@ -14,12 +14,31 @@ variable "container_image" {
default = "ghcr.io/jimmysawczuk/sun-api:latest" default = "ghcr.io/jimmysawczuk/sun-api:latest"
} }
variable "subnets" { variable "zones" {
type = set(string)
default = [
"ap-southeast-2a",
"ap-southeast-2b",
"ap-southeast-2c",
]
}
variable "public_subnets" {
description = "Availability zone for instance associated with ip ranges" description = "Availability zone for instance associated with ip ranges"
type = map(any) type = map(any)
default = { default = {
"ap-southeast-2a" = "10.0.1.0/25" "ap-southeast-2a" = "10.0.1.0/25"
"ap-southeast-2b" = "10.0.2.0/25" "ap-southeast-2b" = "10.0.2.0/25"
"ap-southeast-2c" = "10.0.1.128/25" "ap-southeast-2c" = "10.0.3.0/25"
}
}
variable "private_subnets" {
description = "Availability zone for instance associated with ip ranges"
type = map(any)
default = {
"ap-southeast-2a" = "10.0.1.128/25"
"ap-southeast-2b" = "10.0.2.128/25"
"ap-southeast-2c" = "10.0.3.128/25"
} }
} }

38
aws_fargate_2/alb.tf

@ -0,0 +1,38 @@
resource "aws_lb_target_group" "my_api" {
name = "my-api"
port = 3000
protocol = "HTTP"
target_type = "ip"
vpc_id = aws_vpc.prod_vpc.id
health_check {
enabled = true
path = "/health"
}
depends_on = [aws_alb.my_api]
}
resource "aws_alb" "my_api" {
name = "my-api-lb"
internal = false
load_balancer_type = "application"
subnets = [
aws_subnet.public_a.id,
aws_subnet.public_b.id
]
security_groups = [
aws_security_group.http.id,
aws_security_group.egress_all.id,
]
depends_on = [aws_internet_gateway.igw]
}
resource "aws_alb_listener" "my_api_http" {
load_balancer_arn = aws_alb.my_api.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.my_api.arn
}
}
output "alb_url" {
value = "http://${aws_alb.my_api.dns_name}"
}

13
aws_fargate_2/config.tf

@ -0,0 +1,13 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~>4.0"
}
}
}
provider "aws" {
region = var.region
profile = var.profile
}

39
aws_fargate_2/ecs.tf

@ -0,0 +1,39 @@
resource "aws_ecs_cluster" "my_cluster" {
name = "my_cluster"
}
resource "aws_ecs_task_definition" "my_api" {
family = "my-api"
execution_role_arn = aws_iam_role.my_api_task_execution_role.arn
container_definitions = <<EOF
[
{
"name": "my-api",
"image": "mohitmutha/simplefastifyservice:1.0",
"portMappings": [
{
"containerPort": 3000
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-region": "ap-southeast-2",
"awslogs-group": "/ecs/my-api",
"awslogs-stream-prefix": "ecs"
}
}
}
]
EOF
# These are the minimum values for Fargate containers.
cpu = 256
memory = 512
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
}
resource "aws_cloudwatch_log_group" "my_api" {
name = "/ecs/my-api"
}

24
aws_fargate_2/iam.tf

@ -0,0 +1,24 @@
resource "aws_iam_role" "my_api_task_execution_role" {
name = "my-api-task-execution-role"
assume_role_policy = data.aws_iam_policy_document.ecs_task_assume_role.json
}
data "aws_iam_policy_document" "ecs_task_assume_role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
# Normally we'd prefer not to hardcode an ARN in our Terraform, but since this is
# an AWS-managed policy, it's okay.
data "aws_iam_policy" "ecs_task_execution_role" {
arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
# Attach the above policy to the execution role.
resource "aws_iam_role_policy_attachment" "ecs_task_execution_role" {
role = aws_iam_role.my_api_task_execution_role.name
policy_arn = data.aws_iam_policy.ecs_task_execution_role.arn
}

132
aws_fargate_2/network.tf

@ -0,0 +1,132 @@
resource "aws_vpc" "prod_vpc" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "public_a" {
vpc_id = aws_vpc.prod_vpc.id
cidr_block = "10.0.1.0/25"
availability_zone = "ap-southeast-2a"
tags = {
"Name" = "public | ap-southeast-2a"
}
}
resource "aws_subnet" "public_b" {
vpc_id = aws_vpc.prod_vpc.id
cidr_block = "10.0.1.128/25"
availability_zone = "ap-southeast-2b"
tags = {
"Name" = "public | ap-southeast-2b"
}
}
resource "aws_subnet" "private_a" {
vpc_id = aws_vpc.prod_vpc.id
cidr_block = "10.0.2.0/25"
availability_zone = "ap-southeast-2a"
tags = {
"Name" = "private | ap-southeast-2a"
}
}
resource "aws_subnet" "private_b" {
vpc_id = aws_vpc.prod_vpc.id
cidr_block = "10.0.2.128/25"
availability_zone = "ap-southeast-2b"
tags = {
"Name" = "private | ap-southeast-2b"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.prod_vpc.id
tags = {
"Name" = "public"
}
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.prod_vpc.id
tags = {
"Name" = "private"
}
}
resource "aws_route_table_association" "public_a_subnet" {
subnet_id = aws_subnet.public_a.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private_a_subnet" {
subnet_id = aws_subnet.private_b.id
route_table_id = aws_route_table.private.id
}
resource "aws_eip" "nat" {
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.prod_vpc.id
}
resource "aws_nat_gateway" "ngw" {
subnet_id = aws_subnet.public_a.id
allocation_id = aws_eip.nat.id
depends_on = [aws_internet_gateway.igw]
}
resource "aws_route" "public_igw" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
resource "aws_route" "private_ngw" {
route_table_id = aws_route_table.private.id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.ngw.id
}
resource "aws_security_group" "http" {
name = "http"
description = "HTTP traffic"
vpc_id = aws_vpc.prod_vpc.id
ingress {
from_port = 80
to_port = 80
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "https" {
name = "https"
description = "HTTPS traffic"
vpc_id = aws_vpc.prod_vpc.id
ingress {
from_port = 443
to_port = 443
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "egress_all" {
name = "egress-all"
description = "Allow all outbound traffic"
vpc_id = aws_vpc.prod_vpc.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "ingress_api" {
name = "ingress-api"
description = "Allow ingress to API"
vpc_id = aws_vpc.prod_vpc.id
ingress {
from_port = 3000
to_port = 3000
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"]
}
}

23
aws_fargate_2/service.tf

@ -0,0 +1,23 @@
resource "aws_ecs_service" "my_api" {
name = "my-api"
task_definition = aws_ecs_task_definition.my_api.arn
cluster = aws_ecs_cluster.my_cluster.id
launch_type = "FARGATE"
load_balancer {
target_group_arn = aws_lb_target_group.my_api.arn
container_name = "my-api"
container_port = "3000"
}
desired_count = 1
network_configuration {
assign_public_ip = false
security_groups = [
aws_security_group.egress_all.id,
aws_security_group.ingress_api.id,
]
subnets = [
aws_subnet.private_a.id,
aws_subnet.private_b.id
]
}
}

44
aws_fargate_2/variables.tf

@ -0,0 +1,44 @@
variable "region" {
default = "ap-southeast-2"
}
variable "profile" {
default = "superuser"
}
variable "project" {
default = "template"
}
variable "container_image" {
default = "ghcr.io/jimmysawczuk/sun-api:latest"
}
variable "zones" {
type = set(string)
default = [
"ap-southeast-2a",
"ap-southeast-2b",
"ap-southeast-2c",
]
}
variable "public_subnets" {
description = "Availability zone for instance associated with ip ranges"
type = map(any)
default = {
"ap-southeast-2a" = "10.0.1.0/25"
"ap-southeast-2b" = "10.0.2.0/25"
"ap-southeast-2c" = "10.0.3.0/25"
}
}
variable "private_subnets" {
description = "Availability zone for instance associated with ip ranges"
type = map(any)
default = {
"ap-southeast-2a" = "10.0.1.128/25"
"ap-southeast-2b" = "10.0.2.128/25"
"ap-southeast-2c" = "10.0.3.128/25"
}
}
Loading…
Cancel
Save