diff --git a/README.md b/README.md index f6688cf..5aa7732 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ No modules. | [certificate\_arn](#input\_certificate\_arn) | ARN of an existing SSL certificate for HTTPS | `string` | `""` | no | | [enable\_deletion\_protection](#input\_enable\_deletion\_protection) | Enable or disable deletion protection for the ALB | `bool` | `false` | no | | [enable\_https](#input\_enable\_https) | Enable HTTPS listener (must provide a certificate ARN) | `bool` | `false` | no | +| [enable\_availability\_zone\_all](#input\_enable_availability_zone_all) | Set availability_zone to 'all' for IP targets outside VPC | `bool` | `false` | no | | [health\_check\_healthy\_threshold](#input\_health\_check\_healthy\_threshold) | Number of successful health checks before considering the target healthy | `number` | `3` | no | | [health\_check\_interval](#input\_health\_check\_interval) | Health check interval in seconds | `number` | `30` | no | | [health\_check\_path](#input\_health\_check\_path) | The health check endpoint for ALB target group | `string` | `"/"` | no | diff --git a/example/http-fargate/.terraform.lock.hcl b/example/http-fargate/.terraform.lock.hcl new file mode 100644 index 0000000..92cb2bb --- /dev/null +++ b/example/http-fargate/.terraform.lock.hcl @@ -0,0 +1,63 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.84.0" + constraints = ">= 3.29.0, 5.84.0" + hashes = [ + "h1:dwpeFUdcxgXVAc0JSqO57xf0/r2qOBLPloombCQWFz8=", + "zh:078f77438aba6ec8bf9154b7d223e5c71c48d805d6cd3bcf9db0cc1e82668ac3", + "zh:1f6591ff96be00501e71b792ed3a5a14b21ff03afec9a1c4a3fd9300e6e5b674", + "zh:2ab694e022e81dd74485351c5836148a842ed71cf640664c9d871cb517b09602", + "zh:33c8ccb6e3dc496e828a7572dd981366c6271075c1189f249b9b5236361d7eff", + "zh:6f31068ebad1d627e421c72ccdaafe678c53600ca73714e977bf45ff43ae5d17", + "zh:7488623dccfb639347cae66f9001d39cf06b92e8081975235a1ac3a0ac3f44aa", + "zh:7f042b78b9690a8725c95b91a70fc8e264011b836605bcc342ac297b9ea3937d", + "zh:88b56ac6c7209dc0a775b79975a371918f3aed8f015c37d5899f31deff37c61a", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a1979ba840d704af0932f8de5f541cbb4caa9b6bbd25ed552a24e6772175ba07", + "zh:b058c0533dae580e69d1adbc1f69e6a80632374abfc10e8634d06187a108e87b", + "zh:c88610af9cf957f8dcf4382e0c9ca566ef10e3290f5de01d4d90b2d81b078aa8", + "zh:e9562c055a2247d0c287772b55abef468c79f8d66a74780fe1c5e5dae1a284a9", + "zh:f7a7c71d28441d925a25c08c4485c015b2d9f0338bc9707443e91ff8e161d3d9", + "zh:fee533e81976d0900aa6fa443dc54ef171cbd901847f28a6e8edb1d161fa6fde", + ] +} + +provider "registry.terraform.io/hashicorp/http" { + version = "3.4.5" + hashes = [ + "h1:ceAVZEuaQd7jQX13qf5w7hy3ioiXpuwUaaDRsnAiMLM=", + "zh:2072006c177efc101471f3d5eb8e1d8e6c68778cbfd6db3d3f22f59cfe6ce6ae", + "zh:3ac4cc0efe11ee054300769cfcc37491433937a8824621d1f8f7a18e7401da87", + "zh:63997e5457c9ddf9cfff17bd7bf9f083cbeff3105452045662109dd6be499ef9", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:826819bb8ab7d6e3095f597083d5b1ab93d1854312b9e1b6c18288fff9664f34", + "zh:8ad74e7d8ec2e226a73d49c7c317108f61a4cb803972fb3f945d1709d5115fcd", + "zh:a609ca9e0c91d250ac80295e39d5f524e8c0872d33ba8fde3c3e41893b4b015d", + "zh:ae07d19babc452f63f6a6511b944990e819dc20687b6c8f01d1676812f5ada53", + "zh:b7c827dc32a1a5d77185a78cd391b01217894b384f58169f98a96d683730d8ce", + "zh:d045e3db9f5e39ce78860d3fd94e04604fcbe246f6fe346ee50a971f936e9ccd", + "zh:ec28f9b52c74edd47eebbb5c254a6df5706360cde5ccd65097976efca23a2977", + "zh:f24982eaa7d34fd66554c3cf94873713a0dff14da9ea4c4be0cc76f1a6146d59", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.7.1" + hashes = [ + "h1:/qtweZW2sk0kBNiQM02RvBXmlVdI9oYqRMCyBZ8XA98=", + "zh:3193b89b43bf5805493e290374cdda5132578de6535f8009547c8b5d7a351585", + "zh:3218320de4be943e5812ed3de995946056db86eb8d03aa3f074e0c7316599bef", + "zh:419861805a37fa443e7d63b69fb3279926ccf98a79d256c422d5d82f0f387d1d", + "zh:4df9bd9d839b8fc11a3b8098a604b9b46e2235eb65ef15f4432bde0e175f9ca6", + "zh:5814be3f9c9cc39d2955d6f083bae793050d75c572e70ca11ccceb5517ced6b1", + "zh:63c6548a06de1231c8ee5570e42ca09c4b3db336578ded39b938f2156f06dd2e", + "zh:697e434c6bdee0502cc3deb098263b8dcd63948e8a96d61722811628dce2eba1", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a0b8e44927e6327852bbfdc9d408d802569367f1e22a95bcdd7181b1c3b07601", + "zh:b7d3af018683ef22794eea9c218bc72d7c35a2b3ede9233b69653b3c782ee436", + "zh:d63b911d618a6fe446c65bfc21e793a7663e934b2fef833d42d3ccd38dd8d68d", + "zh:fa985cd0b11e6d651f47cff3055f0a9fd085ec190b6dbe99bf5448174434cdea", + ] +} diff --git a/example/http-fargate/main.tf b/example/http-fargate/main.tf new file mode 100644 index 0000000..83a2da4 --- /dev/null +++ b/example/http-fargate/main.tf @@ -0,0 +1,238 @@ +############################################ +# Provider Configuration +############################################ + +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "5.84.0" + } + } +} + +provider "aws" { + region = "ap-southeast-1" +} + +############################################ +# Data Sources +############################################ + +data "aws_region" "current" {} +data "aws_availability_zones" "available" {} + +data "http" "my_public_ip" { + url = "http://ifconfig.me/ip" +} + +############################################ +# Random Suffix for Resource Names +############################################ + +resource "random_string" "suffix" { + length = 8 + special = false + upper = false +} + +############################################ +# Local Variables +############################################ + +locals { + name = "cltest" + base_name = "${local.name}-${random_string.suffix.result}" + + tags = { + Environment = "dev" + Project = "example" + } +} + +############################################ +# VPC Configuration +############################################ + +module "vpc" { + source = "tfstack/vpc/aws" + + vpc_name = local.base_name + vpc_cidr = "10.0.0.0/16" + availability_zones = slice(data.aws_availability_zones.available.names, 0, 3) + + public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + private_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"] + + eic_subnet = "none" + eic_ingress_cidrs = ["${data.http.my_public_ip.response_body}/32"] + + jumphost_instance_create = false + jumphost_subnet = "10.0.0.0/24" + jumphost_log_prevent_destroy = false + create_igw = true + ngw_type = "single" + + tags = local.tags +} + +############################################ +# AWS ALB Module +############################################ + +module "aws_alb" { + source = "../.." + + name = local.base_name + suffix = random_string.suffix.result + vpc_id = module.vpc.vpc_id + public_subnet_ids = module.vpc.public_subnet_ids + + enable_https = false + http_port = 80 + target_http_port = 80 + target_type = "ip" + + public_subnet_cidrs = module.vpc.public_subnet_cidrs +} + +############################################ +# ECS Cluster Configuration +############################################ + +module "ecs_cluster_fargate" { + source = "tfstack/ecs-cluster-fargate/aws" + + # Core Configuration + cluster_name = local.name + suffix = random_string.suffix.result + + # VPC Configuration + vpc = { + id = module.vpc.vpc_id + private_subnets = [ + for i, subnet in module.vpc.private_subnet_ids : + { id = subnet, cidr = module.vpc.private_subnet_cidrs[i] } + ] + public_subnets = [ + for i, subnet in module.vpc.public_subnet_ids : + { id = subnet, cidr = module.vpc.public_subnet_cidrs[i] } + ] + } + + # Cluster Settings + cluster_settings = [ + { name = "containerInsights", value = "enabled" } + ] + + # Logging Configuration + s3_key_prefix = "logs/" + create_cloudwatch_log_group = true + cloudwatch_log_group_retention_days = 90 + create_s3_logging_bucket = true + + # Capacity Providers + capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 50 + base = 20 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 50 + } + } + } + + ecs_services = [ + { + name = "web-app" + desired_count = 3 + cpu = "256" + memory = "512" + force_new_deployment = true + + execution_role_policies = [ + "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess", + "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess" + ] + + container_definitions = jsonencode([ + { + name = "web-app" + image = "nginx:latest" + cpu = 256 + memory = 512 + essential = true + portMappings = [{ + containerPort = 80 + }] + healthCheck = { + command = ["CMD-SHELL", "curl -f http://127.0.0.1 || exit 1"] + interval = 30 + timeout = 5 + retries = 3 + startPeriod = 10 + } + logConfiguration = { + logDriver = "awslogs" + options = { + awslogs-group = "/aws/ecs/${local.name}-web-app" + awslogs-region = data.aws_region.current.name + awslogs-stream-prefix = "${local.name}-nginx" + } + } + } + ]) + + deployment_minimum_healthy_percent = 100 + deployment_maximum_percent = 200 + health_check_grace_period_seconds = 30 + + subnet_ids = module.vpc.private_subnet_ids + security_groups = [aws_security_group.ecs.id] + assign_public_ip = false + + enable_alb = true + enable_ecs_managed_tags = true + propagate_tags = "TASK_DEFINITION" + + service_tags = { + Environment = "staging" + Project = "WebApp" + Owner = "DevOps" + } + + task_tags = { + TaskType = "backend" + Version = "1.0" + } + } + ] + + ecs_autoscaling = [ + { + service_name = "${local.name}-web-app" + min_capacity = 3 + max_capacity = 12 + scalable_dimension = "ecs:service:DesiredCount" + policy_name = "scale-on-cpu" + policy_type = "TargetTrackingScaling" + target_value = 75 + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + ] + + tags = local.tags +} + +############################################ +# Outputs +############################################ + +output "all_module_outputs" { + description = "All outputs from the ALB module" + value = module.aws_alb +} diff --git a/main.tf b/main.tf index c47156c..88861a7 100644 --- a/main.tf +++ b/main.tf @@ -66,10 +66,11 @@ resource "aws_security_group" "this" { resource "aws_lb_target_group" "http" { count = var.enable_https ? 0 : 1 - name = "${local.base_name}-http" - port = var.target_http_port - protocol = "HTTP" # var.target_protocol - vpc_id = var.vpc_id + name = "${local.base_name}-http" + port = var.target_http_port + protocol = "HTTP" # var.target_protocol + target_type = var.target_type + vpc_id = var.vpc_id health_check { path = var.health_check_path @@ -84,11 +85,12 @@ resource "aws_lb_target_group" "http" { # HTTPS Target Group (Only created if HTTPS is enabled) resource "aws_lb_target_group" "https" { - count = var.enable_https ? 1 : 0 - name = "${local.base_name}-https" - port = var.target_https_port - protocol = "HTTPS" # var.target_protocol - vpc_id = var.vpc_id + count = var.enable_https ? 1 : 0 + name = "${local.base_name}-https" + port = var.target_https_port + protocol = "HTTPS" # var.target_protocol + target_type = var.target_type + vpc_id = var.vpc_id health_check { path = var.health_check_path @@ -170,8 +172,8 @@ resource "aws_lb_target_group_attachment" "generic" { target_id = each.value # Ensure port is only applied for instance and IP targets - port = var.target_type == "instance" || var.target_type == "ip" ? var.target_http_port : null + port = contains(["instance", "ip"], var.target_type) ? var.target_http_port : null # Ensure availability zone is applied only for IP targets outside VPC - availability_zone = var.target_type == "ip" && !contains(var.public_subnet_cidrs, each.value) ? "all" : null + availability_zone = var.enable_availability_zone_all ? "all" : null } diff --git a/variables.tf b/variables.tf index fe12082..e9ee21d 100644 --- a/variables.tf +++ b/variables.tf @@ -145,10 +145,11 @@ variable "certificate_arn" { variable "targets" { description = "List of targets (EC2 instance IDs, IPs, Lambda ARNs, or ALB ARNs)" type = list(string) + default = [] validation { - condition = length(var.targets) > 0 - error_message = "At least one target must be specified." + condition = var.target_type == "ip" || length(var.targets) > 0 + error_message = "At least one target must be specified unless target_type is 'ip'." } } @@ -162,3 +163,9 @@ variable "target_type" { error_message = "Allowed values for target_type are 'instance', 'ip', 'lambda', or 'alb'." } } + +variable "enable_availability_zone_all" { + description = "Set availability_zone to 'all' for IP targets outside VPC" + type = bool + default = false +}