389 lines
9.3 KiB
HCL
389 lines
9.3 KiB
HCL
terraform {
|
|
required_providers {
|
|
aws = {
|
|
source = "hashicorp/aws"
|
|
version = "~> 6.27"
|
|
}
|
|
}
|
|
}
|
|
|
|
provider "aws" {
|
|
region = var.aws_region
|
|
}
|
|
|
|
# Get current AWS account
|
|
data "aws_caller_identity" "current" {}
|
|
|
|
# Reference existing VPC
|
|
data "aws_vpc" "existing" {
|
|
id = var.vpc_id
|
|
}
|
|
|
|
# Reference existing public subnets
|
|
data "aws_subnet" "public" {
|
|
count = length(var.public_subnet_ids)
|
|
id = var.public_subnet_ids[count.index]
|
|
}
|
|
|
|
# Reference existing private subnets (for ECS tasks)
|
|
data "aws_subnet" "private" {
|
|
count = length(var.private_subnet_ids)
|
|
id = var.private_subnet_ids[count.index]
|
|
}
|
|
|
|
# Security Group for ALB (in public subnets)
|
|
resource "aws_security_group" "alb" {
|
|
name = "${var.app_name}-alb-sg"
|
|
description = "Allow inbound traffic to ALB"
|
|
vpc_id = data.aws_vpc.existing.id
|
|
|
|
ingress {
|
|
from_port = 80
|
|
to_port = 80
|
|
protocol = "tcp"
|
|
cidr_blocks = ["3.14.44.224/32"]
|
|
description = "Allow HTTP from internet"
|
|
}
|
|
|
|
egress {
|
|
from_port = 0
|
|
to_port = 0
|
|
protocol = "-1"
|
|
cidr_blocks = ["0.0.0.0/0"]
|
|
description = "Allow all outbound"
|
|
}
|
|
|
|
tags = {
|
|
Name = "${var.app_name}-alb-sg"
|
|
}
|
|
}
|
|
|
|
# Security Group for ECS Tasks (in private subnets)
|
|
resource "aws_security_group" "ecs_tasks" {
|
|
name = "${var.app_name}-ecs-tasks-sg"
|
|
description = "Allow inbound traffic from ALB"
|
|
vpc_id = data.aws_vpc.existing.id
|
|
|
|
ingress {
|
|
from_port = 8000
|
|
to_port = 8000
|
|
protocol = "tcp"
|
|
security_groups = [aws_security_group.alb.id]
|
|
description = "Allow traffic from ALB"
|
|
}
|
|
|
|
egress {
|
|
from_port = 0
|
|
to_port = 0
|
|
protocol = "-1"
|
|
cidr_blocks = ["0.0.0.0/0"]
|
|
description = "Allow all outbound"
|
|
}
|
|
|
|
tags = {
|
|
Name = "${var.app_name}-ecs-tasks-sg"
|
|
}
|
|
}
|
|
|
|
# Application Load Balancer (in public subnets)
|
|
resource "aws_lb" "main" {
|
|
name = "${var.app_name}-alb"
|
|
internal = false
|
|
load_balancer_type = "application"
|
|
security_groups = [aws_security_group.alb.id]
|
|
subnets = var.public_subnet_ids
|
|
|
|
enable_deletion_protection = false
|
|
|
|
tags = {
|
|
Name = "${var.app_name}-alb"
|
|
}
|
|
}
|
|
|
|
# Target Group
|
|
resource "aws_lb_target_group" "app" {
|
|
name = "${var.app_name}-tg"
|
|
port = 8000
|
|
protocol = "HTTP"
|
|
vpc_id = data.aws_vpc.existing.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
|
|
|
|
tags = {
|
|
Name = "${var.app_name}-tg"
|
|
}
|
|
}
|
|
|
|
# ALB Listener
|
|
resource "aws_lb_listener" "app" {
|
|
load_balancer_arn = aws_lb.main.arn
|
|
port = "80"
|
|
protocol = "HTTP"
|
|
|
|
default_action {
|
|
type = "forward"
|
|
target_group_arn = aws_lb_target_group.app.arn
|
|
}
|
|
}
|
|
|
|
# ECS Cluster
|
|
resource "aws_ecs_cluster" "main" {
|
|
name = "${var.app_name}-cluster"
|
|
|
|
tags = {
|
|
Name = "${var.app_name}-cluster"
|
|
}
|
|
}
|
|
|
|
# CloudWatch Log Group
|
|
resource "aws_cloudwatch_log_group" "app" {
|
|
name = "/ecs/${var.app_name}"
|
|
retention_in_days = 7
|
|
|
|
tags = {
|
|
Name = "${var.app_name}-logs"
|
|
}
|
|
}
|
|
|
|
# ECS Task Execution Role
|
|
resource "aws_iam_role" "ecs_task_execution_role" {
|
|
name = "${var.app_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"
|
|
}
|
|
}]
|
|
})
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" {
|
|
role = aws_iam_role.ecs_task_execution_role.name
|
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
|
|
}
|
|
resource "aws_iam_role_policy" "bedrock_policy" {
|
|
name = "${var.app_name}-bedrock-policy"
|
|
role = aws_iam_role.ecs_task_role.id
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [{
|
|
Effect = "Allow"
|
|
Action = [
|
|
"bedrock:InvokeModel",
|
|
"bedrock:InvokeModelWithResponseStream",
|
|
"bedrock:GetInferenceProfile"
|
|
]
|
|
Resource = "*"
|
|
}]
|
|
})
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "s3_policy" {
|
|
name = "${var.app_name}-s3-policy"
|
|
role = aws_iam_role.ecs_task_role.id
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
Action = [
|
|
"s3:GetObject",
|
|
"s3:PutObject"
|
|
]
|
|
Resource = "arn:aws:s3:::upflux-doc-analyzer/*"
|
|
},
|
|
{
|
|
Effect = "Allow"
|
|
Action = [
|
|
"s3:DeleteObject"
|
|
]
|
|
Resource = "arn:aws:s3:::upflux-doc-analyzer/temp_textract/*"
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "textract_policy" {
|
|
name = "${var.app_name}-textract-policy"
|
|
role = aws_iam_role.ecs_task_role.id
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [{
|
|
Effect = "Allow"
|
|
Action = [
|
|
"textract:DetectDocumentText",
|
|
"textract:StartDocumentTextDetection",
|
|
"textract:GetDocumentTextDetection"
|
|
]
|
|
Resource = "*"
|
|
}]
|
|
})
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "secrets_manager_policy" {
|
|
name = "${var.app_name}-secrets-manager-policy"
|
|
role = aws_iam_role.ecs_task_role.id
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [{
|
|
Effect = "Allow"
|
|
Action = [
|
|
"secretsmanager:GetSecretValue"
|
|
]
|
|
Resource = "*"
|
|
}]
|
|
})
|
|
}
|
|
|
|
# ECS Task Definition
|
|
resource "aws_ecs_task_definition" "app" {
|
|
family = var.app_name
|
|
network_mode = "awsvpc"
|
|
requires_compatibilities = ["FARGATE"]
|
|
cpu = var.fargate_cpu
|
|
memory = var.fargate_memory
|
|
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
|
|
task_role_arn = aws_iam_role.ecs_task_role.arn
|
|
container_definitions = jsonencode([{
|
|
name = var.app_name
|
|
image = "${data.aws_caller_identity.current.account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/${var.ecr_repository_name}:${var.image_tag}"
|
|
|
|
environment = [
|
|
{
|
|
name = "LANGFUSE_HOST"
|
|
value = var.langfuse_host
|
|
}
|
|
]
|
|
|
|
portMappings = [{
|
|
containerPort = 8000
|
|
hostPort = 8000
|
|
protocol = "tcp"
|
|
}]
|
|
|
|
logConfiguration = {
|
|
logDriver = "awslogs"
|
|
options = {
|
|
"awslogs-group" = aws_cloudwatch_log_group.app.name
|
|
"awslogs-region" = var.aws_region
|
|
"awslogs-stream-prefix" = "ecs"
|
|
}
|
|
}
|
|
|
|
healthCheck = {
|
|
command = ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]
|
|
interval = 30
|
|
timeout = 5
|
|
retries = 3
|
|
startPeriod = 60
|
|
}
|
|
}])
|
|
|
|
tags = {
|
|
Name = "${var.app_name}-task"
|
|
}
|
|
}
|
|
|
|
# ECS Service (tasks in private subnets)
|
|
resource "aws_ecs_service" "app" {
|
|
name = "${var.app_name}-service"
|
|
cluster = aws_ecs_cluster.main.id
|
|
task_definition = aws_ecs_task_definition.app.arn
|
|
desired_count = var.app_count
|
|
launch_type = "FARGATE"
|
|
|
|
network_configuration {
|
|
security_groups = [aws_security_group.ecs_tasks.id]
|
|
subnets = var.private_subnet_ids # ECS tasks in private subnets
|
|
assign_public_ip = false # No public IP needed with NAT gateway
|
|
}
|
|
|
|
load_balancer {
|
|
target_group_arn = aws_lb_target_group.app.arn
|
|
container_name = var.app_name
|
|
container_port = 8000
|
|
}
|
|
|
|
depends_on = [aws_lb_listener.app]
|
|
|
|
tags = {
|
|
Name = "${var.app_name}-service"
|
|
}
|
|
}
|
|
# Autoscaling
|
|
resource "aws_appautoscaling_target" "ecs" {
|
|
max_capacity = 3
|
|
min_capacity = 1
|
|
resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.app.name}"
|
|
scalable_dimension = "ecs:service:DesiredCount"
|
|
service_namespace = "ecs"
|
|
}
|
|
|
|
resource "aws_appautoscaling_policy" "cpu" {
|
|
name = "${var.app_name}-cpu-autoscaling"
|
|
policy_type = "TargetTrackingScaling"
|
|
resource_id = aws_appautoscaling_target.ecs.resource_id
|
|
scalable_dimension = aws_appautoscaling_target.ecs.scalable_dimension
|
|
service_namespace = aws_appautoscaling_target.ecs.service_namespace
|
|
|
|
target_tracking_scaling_policy_configuration {
|
|
predefined_metric_specification {
|
|
predefined_metric_type = "ECSServiceAverageCPUUtilization"
|
|
}
|
|
target_value = 70.0
|
|
}
|
|
}
|
|
|
|
resource "aws_appautoscaling_policy" "memory" {
|
|
name = "${var.app_name}-memory-autoscaling"
|
|
policy_type = "TargetTrackingScaling"
|
|
resource_id = aws_appautoscaling_target.ecs.resource_id
|
|
scalable_dimension = aws_appautoscaling_target.ecs.scalable_dimension
|
|
service_namespace = aws_appautoscaling_target.ecs.service_namespace
|
|
|
|
target_tracking_scaling_policy_configuration {
|
|
predefined_metric_specification {
|
|
predefined_metric_type = "ECSServiceAverageMemoryUtilization"
|
|
}
|
|
target_value = 80.0
|
|
}
|
|
}
|
|
|
|
#ECS Task Role (for application to call AWS services)
|
|
resource "aws_iam_role" "ecs_task_role" {
|
|
name = "${var.app_name}-ecs-task-role"
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [{
|
|
Action = "sts:AssumeRole"
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Service = "ecs-tasks.amazonaws.com"
|
|
}
|
|
}]
|
|
})
|
|
}
|
|
|