Files
nvhi-atsila-microservice/infrastructure/services/main.tf

478 lines
12 KiB
Terraform
Raw Normal View History

2025-08-05 13:41:43 +00:00
# Phase 2: ECS Fargate + Application Load Balancer
# File: infrastructure/services/main.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = var.common_tags
}
}
# Data sources to reference Foundation Layer outputs
data "terraform_remote_state" "foundation" {
backend = "s3"
config = {
bucket = "nvhi-atsila-microservice-terraform-state-c4ae0f80"
key = "foundation/terraform.tfstate"
region = var.aws_region
}
}
# Enterprise naming convention with AWS resource limits
locals {
# Shortened base name for AWS resource naming limits
base_name = "nvhi-atsila"
# Computed names ensuring AWS limits compliance
# Target Groups (32 char limit): "nvhi-atsila-blue-tg" = 17 chars ✅
blue_tg_name = "${local.base_name}-blue-tg"
green_tg_name = "${local.base_name}-green-tg"
# ALB name (32 char limit): "nvhi-atsila-alb" = 15 chars ✅
alb_name = "${local.base_name}-alb"
# ECS names for consistency
ecs_cluster_name = "${local.base_name}-cluster"
ecs_service_name = "${local.base_name}-service"
# ECR name
ecr_repo_name = "${local.base_name}-app"
# Common resource prefix for tagging and organization
resource_prefix = local.base_name
}
# ECR Repository for container images
resource "aws_ecr_repository" "app" {
name = local.ecr_repo_name
image_tag_mutability = "MUTABLE"
force_delete = true # For demo purposes
image_scanning_configuration {
scan_on_push = true
}
tags = {
Name = "${var.project_name}-ecr"
Environment = var.environment
Project = var.project_name
}
}
# ECR Lifecycle Policy
resource "aws_ecr_lifecycle_policy" "app" {
repository = aws_ecr_repository.app.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Keep last 10 images"
selection = {
tagStatus = "tagged"
tagPrefixList = ["v"]
countType = "imageCountMoreThan"
countNumber = 10
}
action = {
type = "expire"
}
},
{
rulePriority = 2
description = "Delete untagged images older than 1 day"
selection = {
tagStatus = "untagged"
countType = "sinceImagePushed"
countUnit = "days"
countNumber = 1
}
action = {
type = "expire"
}
}
]
})
}
# ECS Cluster
resource "aws_ecs_cluster" "main" {
name = local.ecs_cluster_name
configuration {
execute_command_configuration {
logging = "OVERRIDE"
log_configuration {
cloud_watch_log_group_name = aws_cloudwatch_log_group.ecs_cluster.name
}
}
}
setting {
name = "containerInsights"
value = "enabled"
}
tags = {
Name = "${var.project_name}-ecs-cluster"
Environment = var.environment
Project = var.project_name
}
}
# CloudWatch Log Group for ECS
resource "aws_cloudwatch_log_group" "ecs_cluster" {
name = "/aws/ecs/${local.ecs_cluster_name}"
retention_in_days = 7 # Free tier friendly
skip_destroy = false
tags = {
Name = "${local.ecs_cluster_name}-logs"
Environment = var.environment
Project = var.project_name
}
}
resource "aws_cloudwatch_log_group" "app" {
name = "/aws/ecs/${local.ecr_repo_name}"
retention_in_days = 7 # Free tier friendly
skip_destroy = false
tags = {
Name = "${local.ecr_repo_name}-logs"
Environment = var.environment
Project = var.project_name
}
}
# Application Load Balancer
resource "aws_lb" "main" {
name = local.alb_name
internal = false
load_balancer_type = "application"
security_groups = [data.terraform_remote_state.foundation.outputs.alb_security_group_id]
subnets = data.terraform_remote_state.foundation.outputs.public_subnet_ids
enable_deletion_protection = false # For demo purposes
tags = {
Name = "${var.project_name}-alb"
Environment = var.environment
Project = var.project_name
}
}
# ALB Target Group for Blue Environment
resource "aws_lb_target_group" "blue" {
name = local.blue_tg_name
port = 8080
protocol = "HTTP"
vpc_id = data.terraform_remote_state.foundation.outputs.vpc_id
target_type = "ip"
health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = "/health"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 3
}
deregistration_delay = 30 # Fast for demo
tags = {
Name = "${var.project_name}-blue-tg"
Environment = var.environment
Project = var.project_name
}
}
# ALB Target Group for Green Environment (Blue/Green Deployment)
resource "aws_lb_target_group" "green" {
name = local.green_tg_name
port = 8080
protocol = "HTTP"
vpc_id = data.terraform_remote_state.foundation.outputs.vpc_id
target_type = "ip"
health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = "/health"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 3
}
deregistration_delay = 30 # Fast for demo
tags = {
Name = "${var.project_name}-green-tg"
Environment = var.environment
Project = var.project_name
}
}
# ALB Listener (defaults to Blue)
resource "aws_lb_listener" "main" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
forward {
target_group {
arn = aws_lb_target_group.blue.arn
}
}
}
tags = {
Name = "${var.project_name}-alb-listener"
Environment = var.environment
Project = var.project_name
}
}
# IAM Role for ECS Task Execution
resource "aws_iam_role" "ecs_task_execution" {
name = "${var.project_name}-ecs-task-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
tags = {
Name = "${var.project_name}-ecs-task-execution-role"
Environment = var.environment
Project = var.project_name
}
}
# IAM Role Policy Attachment for ECS Task Execution
resource "aws_iam_role_policy_attachment" "ecs_task_execution" {
role = aws_iam_role.ecs_task_execution.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
# Additional IAM Policy for ECR and CloudWatch
resource "aws_iam_role_policy" "ecs_task_execution_custom" {
name = "${var.project_name}-ecs-task-execution-custom"
role = aws_iam_role.ecs_task_execution.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
]
Resource = "*"
},
{
Effect = "Allow"
Action = [
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "${aws_cloudwatch_log_group.app.arn}:*"
}
]
})
}
# IAM Role for ECS Task (application permissions)
resource "aws_iam_role" "ecs_task" {
name = "${var.project_name}-ecs-task-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
tags = {
Name = "${var.project_name}-ecs-task-role"
Environment = var.environment
Project = var.project_name
}
}
# ECS Task Definition
resource "aws_ecs_task_definition" "app" {
family = local.ecr_repo_name
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = var.task_cpu
memory = var.task_memory
execution_role_arn = aws_iam_role.ecs_task_execution.arn
task_role_arn = aws_iam_role.ecs_task.arn
container_definitions = jsonencode([
{
name = "app"
image = "${aws_ecr_repository.app.repository_url}:${var.image_tag}"
essential = true
portMappings = [
{
containerPort = 8080
protocol = "tcp"
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = aws_cloudwatch_log_group.app.name
awslogs-region = var.aws_region
awslogs-stream-prefix = "ecs"
}
}
environment = [
{
name = "ENV"
value = var.environment
},
{
name = "PROJECT_NAME"
value = var.project_name
}
]
}
])
tags = {
Name = "${var.project_name}-task-definition"
Environment = var.environment
Project = var.project_name
}
}
# ECS Service (Blue Environment)
resource "aws_ecs_service" "app" {
name = local.ecs_service_name
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = var.desired_count
launch_type = "FARGATE"
network_configuration {
subnets = data.terraform_remote_state.foundation.outputs.app_subnet_ids
security_groups = [data.terraform_remote_state.foundation.outputs.ecs_tasks_security_group_id]
assign_public_ip = true # Required for public subnets without NAT gateway
}
load_balancer {
target_group_arn = aws_lb_target_group.blue.arn
container_name = "app"
container_port = 8080
}
depends_on = [aws_lb_listener.main]
# Blue/Green deployment configuration
maximum_percent = 200
minimum_healthy_percent = 50
2025-08-06 04:42:15 +00:00
deployment_circuit_breaker {
enable = true
rollback = true
2025-08-05 13:41:43 +00:00
}
2025-08-06 04:42:15 +00:00
2025-08-05 13:41:43 +00:00
enable_execute_command = true
tags = {
Name = "${var.project_name}-ecs-service"
Environment = var.environment
Project = var.project_name
}
}
# CloudWatch Alarms for monitoring
resource "aws_cloudwatch_metric_alarm" "high_cpu" {
alarm_name = "${var.project_name}-high-cpu"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
period = "300"
statistic = "Average"
threshold = "80"
alarm_description = "This metric monitors ECS service CPU utilization"
alarm_actions = [] # Add SNS topic ARN for notifications
dimensions = {
ServiceName = aws_ecs_service.app.name
ClusterName = aws_ecs_cluster.main.name
}
tags = {
Name = "${var.project_name}-high-cpu-alarm"
Environment = var.environment
Project = var.project_name
}
}
resource "aws_cloudwatch_metric_alarm" "high_memory" {
alarm_name = "${var.project_name}-high-memory"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "MemoryUtilization"
namespace = "AWS/ECS"
period = "300"
statistic = "Average"
threshold = "80"
alarm_description = "This metric monitors ECS service memory utilization"
alarm_actions = [] # Add SNS topic ARN for notifications
dimensions = {
ServiceName = aws_ecs_service.app.name
ClusterName = aws_ecs_cluster.main.name
}
tags = {
Name = "${var.project_name}-high-memory-alarm"
Environment = var.environment
Project = var.project_name
}
}