ECSでnginxを構築する

nginxをECSで構築してみました。

```

├── auto-scale.tf
├── ecs.tf
├── load_balancer.tf
├── network.tf
├── outputs.tf
├── provider.tf
├── security.tf
├── templates
│   └── task-definition.json
├── terraform.tfstate
├── terraform.tfvars.example
└── variables.tf
```

```/templete/task-definition.json
[
  {
    "name": "nginx",
    "image": "${app_image}",
    "cpu": ${app_cpu},
    "memory": ${app_memory},
    "networkMode": "awsvpc",
    "portMappings": [
      {
        "containerPort": 80,
        "hostPort": 80
      }
    ]
  }
]
```

```auto-scale.tf
# Create an autoscaling based on the cluster and service
resource "aws_appautoscaling_target" "ecs_target" {
  max_capacity       = var.autoscale_config.max
  min_capacity       = var.autoscale_config.min
  resource_id        = "service/${aws_ecs_cluster.ecs.name}/${aws_ecs_service.service.name}"
  scalable_dimension = "ecs:service:DesiredCount"
  service_namespace  = "ecs"
}

# =========== Target Scaling 
# This is an example: app/EC2Co-EcsEl-1TKLTMITMM0EO/f37c06a68c1748aa/targetgroup/EC2Co-Defau-LDNM7Q3ZH1ZN/6d4ea56ca2d6a18d.
resource "aws_appautoscaling_policy" "target_scaling" {
  name               = "nginx-target-policy"
  policy_type        = "TargetTrackingScaling"
  resource_id        = aws_appautoscaling_target.ecs_target.resource_id
  scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension
  service_namespace  = aws_appautoscaling_target.ecs_target.service_namespace
  target_tracking_scaling_policy_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ALBRequestCountPerTarget"
      resource_label         = "app/${aws_alb.lb.name}/${basename("${aws_alb.lb.id}")}/targetgroup/${aws_alb_target_group.target.name}/${basename("${aws_alb_target_group.target.id}")}"
    }
    target_value = 10
  }
}

# # =========== Step Scaling
# # Scale up
# resource "aws_appautoscaling_policy" "up_policy" {
#   name               = "nginx_scale_up_policy"
#   resource_id        = aws_appautoscaling_target.ecs_target.resource_id
#   scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension
#   service_namespace  = aws_appautoscaling_target.ecs_target.service_namespace

#   step_scaling_policy_configuration {
#     adjustment_type         = "ChangeInCapacity"
#     cooldown                = var.autoscale_metric.cooldown
#     metric_aggregation_type = "Maximum"
#     step_adjustment {
#       metric_interval_lower_bound = 0
#       scaling_adjustment          = var.autoscale_metric.up
#     }
#   }
# }

# # Scale down
# resource "aws_appautoscaling_policy" "down_policy" {
#   name               = "nginx-scale-down-policy"
#   resource_id        = aws_appautoscaling_target.ecs_target.resource_id
#   scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension
#   service_namespace  = aws_appautoscaling_target.ecs_target.service_namespace

#   step_scaling_policy_configuration {
#     adjustment_type         = "ChangeInCapacity"
#     cooldown                = var.autoscale_metric.cooldown
#     metric_aggregation_type = "Maximum"

#     step_adjustment {
#       metric_interval_upper_bound = 0
#       scaling_adjustment          = var.autoscale_metric.down
#     }
#   }
# }

# # =========== Define metrics for the cpu
# # High
# resource "aws_cloudwatch_metric_alarm" "high_cpu_service" {
#   alarm_name          = "nginx-cpu-high"
#   comparison_operator = "GreaterThanOrEqualToThreshold"
#   period              = var.autoscale_metric.period
#   evaluation_periods  = var.autoscale_metric.evaluation_periods
#   metric_name         = "CPUUtilization"
#   namespace           = "AWS/ECS"
#   statistic           = "Average"
#   threshold           = var.autoscale_metric.max_threshold

#   dimensions = {
#     ClusterName = aws_ecs_cluster.ecs.name
#     ServiceName = aws_ecs_service.service.name
#   }

#   alarm_actions = [aws_appautoscaling_policy.up_policy.arn]
# }

# # Low
# resource "aws_cloudwatch_metric_alarm" "low_cpu_service" {
#   alarm_name          = "nginx-cpu-down"
#   comparison_operator = "LessThanOrEqualToThreshold"
#   period              = var.autoscale_metric.period
#   evaluation_periods  = var.autoscale_metric.evaluation_periods
#   metric_name         = "CPUUtilization"
#   namespace           = "AWS/ECS"
#   statistic           = "Average"
#   threshold           = var.autoscale_metric.min_threshold

#   dimensions = {
#     ClusterName = aws_ecs_cluster.ecs.name
#     ServiceName = aws_ecs_service.service.name
#   }

#   alarm_actions = [aws_appautoscaling_policy.down_policy.arn]
# }
```

```ecs.tf
# =========== Creating aws ecs cluster
resource "aws_ecs_cluster" "ecs" {
  name = "nginx-cluster"
}

# =========== Creating task definition
data "template_file" "nginx" {
  template = file("./templates/task-definition.json")

  vars = {
    app_image  = var.app_type.image
    app_cpu    = var.aws_launch_type.cpu
    app_memory = var.aws_launch_type.memory
  }
}

resource "aws_ecs_task_definition" "td" {
  family                   = "nginx-task"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = var.aws_launch_type.cpu
  memory                   = var.aws_launch_type.memory
  container_definitions    = data.template_file.nginx.rendered
}

# =========== Creating service for the cluster
resource "aws_ecs_service" "service" {
  name            = "nginx-service"
  cluster         = aws_ecs_cluster.ecs.id
  task_definition = aws_ecs_task_definition.td.arn
  desired_count   = var.app_type.count
  launch_type     = var.aws_launch_type.type

  network_configuration {
    security_groups  = [aws_security_group.ecs.id]
    subnets          = aws_subnet.private.*.id
    assign_public_ip = true
  }

  load_balancer {
    target_group_arn = aws_alb_target_group.target.id
    container_name   = "nginx"
    container_port   = var.app_type.port
  }

  depends_on = [
    aws_alb_listener.listen,
  ]
}
```

```load_balancer.tf
# =========== Creating a load balancer
resource "aws_alb" "lb" {
  name            = "nginx-load-balancer"
  subnets         = aws_subnet.public.*.id
  security_groups = [aws_security_group.lb.id]
}

# =========== Createing a target group
resource "aws_alb_target_group" "target" {
  vpc_id      = aws_vpc.main.id
  name        = "nginx-target-group"
  port        = var.app_type.port
  protocol    = "HTTP"
  target_type = "ip"

  health_check {
    path    = "/"
    matcher = "200"
  }
}

# =========== Redirect incoming traffic to target from lb
resource "aws_alb_listener" "listen" {
  load_balancer_arn = aws_alb.lb.arn
  port              = var.app_type.port

  default_action {
    type             = "forward"
    target_group_arn = aws_alb_target_group.target.arn
  }
}

```

```network.tf
# =========== Get all available zones in current region
data "aws_availability_zones" "az" {
  state = "available"
}

# =========== Creating vpc
resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr_block
}

# =========== Creating 2 /24 private subnet 
resource "aws_subnet" "private" {
  vpc_id            = aws_vpc.main.id
  count             = var.availability_zones_count
  availability_zone = data.aws_availability_zones.az.names[count.index]
  cidr_block        = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
}

# =========== Creating 2 /24 public subnet 
# Adding availability_zones_count to differ the cidr numnetwork
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  count                   = var.availability_zones_count
  availability_zone       = data.aws_availability_zones.az.names[count.index]
  cidr_block              = cidrsubnet(aws_vpc.main.cidr_block, 8, var.availability_zones_count + count.index)
  map_public_ip_on_launch = true
}

# Create gateway to make public accessible
resource "aws_internet_gateway" "gateway" {
  vpc_id = aws_vpc.main.id
}


# =========== Create gateway for nat

# Elastic IP
resource "aws_eip" "nat" {
  count      = var.availability_zones_count
  depends_on = [aws_internet_gateway.gateway]
  vpc        = true
}

# NAT 
# Element needed to map to each private ip
resource "aws_nat_gateway" "gateway" {
  count         = var.availability_zones_count
  subnet_id     = element(aws_subnet.public.*.id, count.index)
  allocation_id = element(aws_eip.nat.*.id, count.index)
}

# ============ Routing table

# Public
resource "aws_route" "internet" {
  route_table_id         = aws_vpc.main.main_route_table_id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.gateway.id
}

# Private
resource "aws_route_table" "private" {
  count  = var.availability_zones_count
  vpc_id = aws_vpc.main.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = element(aws_nat_gateway.gateway.*.id, count.index)
  }
}

# =========== Attaching routing table to the private subnet
resource "aws_route_table_association" "private" {
  count          = var.availability_zones_count
  subnet_id      = element(aws_subnet.private.*.id, count.index)
  route_table_id = element(aws_route_table.private.*.id, count.index)
}

```

```outputs.tf
# =========== Outputing the created dns server
output "alb_hostname" {
  value = aws_alb.lb.dns_name
}
```

```provider.tf
# =========== Creating AWS Provider
provider "aws" {
  region = var.region
  access_key = var.aws_access_key_id
  secret_key = var.aws_secret_access_key
}

```

```security.tf

# =========== Creating security group for load balancer
resource "aws_security_group" "lb" {
  name = "nginx-load-balancer-security-group"
  description = "Allow http to be accepted and forwared to 80"
  vpc_id = aws_vpc.main.id

  # inbound rule accept 80:80
  ingress {
    protocol = "tcp"
    description = "Accepting 80 and forward to 80"
    from_port = var.app_type.port
    to_port = var.app_type.port
    cidr_blocks = ["0.0.0.0/0"]
  }

  # outbound rule
  egress {
    protocol = "-1" # set to all
    from_port = 0
    to_port = 0
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# =========== Creating security group for ecs
resource "aws_security_group" "ecs" {
  name = "nginx-ecs-tasks-security-group"
  description = "Limiting access of ecs to get from alb only"
  vpc_id = aws_vpc.main.id

  # inbound rule accept 80:80 for lb only
  ingress {
    protocol = "tcp"
    description = "Accepting 80 and forward to 80"
    from_port = var.app_type.port
    to_port = var.app_type.port
    security_groups = [aws_security_group.lb.id]
  }

  # outbound rule
  egress {
    protocol = "-1" # set to all
    from_port = 0
    to_port = 0
    cidr_blocks = ["0.0.0.0/0"]
  }
}
```

```variables.tf

variable "aws_access_key_id" {
  description = "Your aws access key id"
  type        = string
}

variable "aws_secret_access_key" {
  description = "Your aws secret access key"
  type        = string
}

variable "region" {
  description = "AWS region"
  default     = "us-east-1"
  type        = string
}

# What image you want to build
# How many container should be spawned
# port
variable "app_type" {
  description = "Application and configuration"
  type = object({
    image = string
    count = number
    port  = number
  })
  default = {
    image = "nginx:latest"
    count = 2
    port  = 80
  }
}

variable "aws_launch_type" {
  description = "ECS Launch type. (1vCPU = 1024, memory in MiB)"
  type = object({
    type   = string
    cpu    = number
    memory = number
  })
  default = {
    type   = "FARGATE"
    cpu    = 256
    memory = 512
  }
}

variable "availability_zones_count" {
  description = "Many instance that will be created"
  type        = number
  default     = 2
}

variable "vpc_cidr_block" {
  description = "CIDR block for your vpc"
  type        = string
  default     = "172.16.0.0/16" # 16 bit hosts, 2^16 which is maximal
}

variable "autoscale_config" {
  description = "Configuration for app autoscaling target"
  type = object({
    min = number
    max = number
  })
  default = {
    min = 1
    max = 4
  }
}

variable "autoscale_metric" {
  description = "Configuration for cloud metric alarm"
  type = object({
    period             = string
    cooldown           = number
    evaluation_periods = string
    max_threshold      = string
    min_threshold      = string
    up                 = number
    down               = number
  })
  default = {
    period             = "120"
    cooldown           = 120
    evaluation_periods = "3"
    max_threshold      = "80"
    min_threshold      = "10"
    up                 = 1
    down               = -1
  }
}

```

ここにリージョンとaws_access_key_idとaws_secret_access_key入れる。

```terraform.tfvars.example

region = "prefered-region"
aws_access_key_id = "YOUR_AWS_ACCESS_KEY"
aws_secret_access_key = "YOUR_AWS_SECRET_KEY"

```