diff --git a/terraform/aws/modules/composition/envoy-proxy/data.tf b/terraform/aws/modules/composition/envoy-proxy/data.tf index eb58f21..cd8c811 100644 --- a/terraform/aws/modules/composition/envoy-proxy/data.tf +++ b/terraform/aws/modules/composition/envoy-proxy/data.tf @@ -1,3 +1,70 @@ +# ========================================================================= +# Data Sources +# ========================================================================= + data "aws_region" "current" {} data "aws_caller_identity" "current" {} + +# ========================================================================= +# Blue-Green Deployment Data Sources +# ========================================================================= + +# Find existing blue ASG for blue-green deployments +# This assumes the blue ASG is already tagged appropriately +data "aws_autoscaling_groups" "groups_blue" { + + # Filter by common tags to find ASGs from this module + dynamic "filter" { + for_each = local.common_tags + + content { + name = "tag:${filter.key}" + values = [filter.value] + } + } + + # Filter specifically for blue deployment + filter { + name = "tag:Deployment" + values = ["blue"] + } +} + +# Find existing green ASG for blue-green deployments +data "aws_autoscaling_groups" "groups_green" { + + # Filter by common tags to find ASGs from this module + dynamic "filter" { + for_each = local.common_tags + + content { + name = "tag:${filter.key}" + values = [filter.value] + } + } + + # Filter specifically for blue deployment + filter { + name = "tag:Deployment" + values = ["green"] + } +} + +# Get details of the blue ASG +data "aws_autoscaling_group" "asg_blue" { + count = length(data.aws_autoscaling_groups.groups_blue.names) > 0 ? 1 : 0 + name = data.aws_autoscaling_groups.groups_blue.names[0] +} + +# Get details of the green ASG +data "aws_autoscaling_group" "asg_green" { + count = length(data.aws_autoscaling_groups.groups_green.names) > 0 ? 1 : 0 + name = data.aws_autoscaling_groups.groups_green.names[0] +} + +# Reference to existing IAM role (if using existing) +data "aws_iam_role" "existing_envoy_role" { + count = var.create_iam_role ? 0 : 1 + name = var.existing_iam_role_name +} diff --git a/terraform/aws/modules/composition/envoy-proxy/locals.tf b/terraform/aws/modules/composition/envoy-proxy/locals.tf index ce730fa..f1967ed 100644 --- a/terraform/aws/modules/composition/envoy-proxy/locals.tf +++ b/terraform/aws/modules/composition/envoy-proxy/locals.tf @@ -65,5 +65,59 @@ locals { # Launch Template selection - use created or existing launch_template_id = var.use_existing_launch_template ? var.existing_launch_template_id : aws_launch_template.envoy[0].id - launch_template_version = var.use_existing_launch_template ? var.existing_launch_template_version : "$Latest" + launch_template_version = var.use_existing_launch_template ? var.existing_launch_template_version : aws_launch_template.envoy[0].latest_version + + enable_bg_rollout = var.blue_green_rollout != null && length(try(data.aws_autoscaling_groups.groups_blue.names, [])) > 0 + + rollout_version = length(try(data.aws_autoscaling_groups.groups_blue.names, [])) > 0 ? ( + tonumber( + try( + [for t in data.aws_autoscaling_group.asg_blue[0].tag : t.value if t.key == "Version"][0], + "0" + ) + ) + ) : 0 + + standard_version = length(try(data.aws_autoscaling_groups.groups_green.names, [])) > 0 ? ( + tonumber( + try( + [for t in data.aws_autoscaling_group.asg_green[0].tag : t.value if t.key == "Version"][0], + "0" + ) + ) + ) : local.rollout_version + + target_group_arns = var.create_target_group ? [aws_lb_target_group.envoy[local.standard_version].arn] : [var.existing_tg_arn] + + deployments = local.enable_bg_rollout ? { + (local.rollout_version) = { + lt_version = data.aws_autoscaling_group.asg_blue[0].launch_template[0].version + lt_id = data.aws_autoscaling_group.asg_blue[0].launch_template[0].id + deployment = "blue" + target_group_arns = data.aws_autoscaling_group.asg_blue[0].target_group_arns + weight = var.blue_green_rollout.blue_weight + }, + (local.rollout_version + 1) = { + lt_version = local.launch_template_version + lt_id = local.launch_template_id + deployment = "green" + target_group_arns = local.target_group_arns + weight = var.blue_green_rollout.green_weight + } + } : { + (local.standard_version) = { + lt_version = local.launch_template_version + lt_id = local.launch_template_id + deployment = "blue" + target_group_arns = local.target_group_arns + weight = 100 + } + } + + target_groups = local.enable_bg_rollout ? { + (local.rollout_version) = "blue", + (local.rollout_version + 1) = "green" + } : { + (local.standard_version) = "blue" + } } diff --git a/terraform/aws/modules/composition/envoy-proxy/main.tf b/terraform/aws/modules/composition/envoy-proxy/main.tf index 3c06e1e..4e4df2b 100644 --- a/terraform/aws/modules/composition/envoy-proxy/main.tf +++ b/terraform/aws/modules/composition/envoy-proxy/main.tf @@ -191,12 +191,6 @@ module "envoy_iam_role" { tags = local.common_tags } -# Reference to existing IAM role (if using existing) -data "aws_iam_role" "existing_envoy_role" { - count = var.create_iam_role ? 0 : 1 - name = var.existing_iam_role_name -} - # Create a new instance profile for existing IAM role # This allows you to reuse an existing IAM role but create a fresh instance profile resource "aws_iam_instance_profile" "envoy_profile" { @@ -366,9 +360,9 @@ module "alb" { # Target group for ALB → Envoy ASG # Targets Envoy traffic listener port resource "aws_lb_target_group" "envoy" { - count = var.create_target_group ? 1 : 0 + for_each = var.create_target_group ? local.target_groups : {} - name = "${local.name_prefix}-tg-${substr(md5("${var.target_group_protocol}-${var.envoy_traffic_port}"), 0, 6)}" + name = "${local.name_prefix}-tg-${substr(md5("${var.target_group_protocol}-${var.envoy_traffic_port}"), 0, 6)}-v${each.key}" port = var.envoy_traffic_port protocol = var.target_group_protocol # HTTP or HTTPS based on configuration vpc_id = var.vpc_id @@ -390,7 +384,9 @@ resource "aws_lb_target_group" "envoy" { tags = merge( local.common_tags, { - Name = "${local.name_prefix}-tg" + Name = "${local.name_prefix}-tg" + Deployment = each.value + Version = each.key } ) @@ -426,8 +422,26 @@ resource "aws_lb_listener" "envoy_http" { } } - # Forward to target group (only used if type = "forward") - target_group_arn = var.enable_https_listener && var.enable_http_to_https_redirect ? null : (var.create_target_group ? aws_lb_target_group.envoy[0].arn : var.existing_tg_arn) + # Weighted Forward block (used only when not redirecting) + dynamic "forward" { + for_each = !(var.enable_https_listener && var.enable_http_to_https_redirect) ? [1] : [] + content { + + # Canary target group (optional) + dynamic "target_group" { + for_each = local.deployments + content { + arn = each.value.target_group_arns[0] + weight = each.value.weight + } + } + + stickiness { + enabled = false + duration = 1 + } + } + } } tags = local.common_tags @@ -444,8 +458,23 @@ resource "aws_lb_listener" "envoy_https" { certificate_arn = var.ssl_certificate_arn default_action { - type = "forward" - target_group_arn = var.create_target_group ? aws_lb_target_group.envoy[0].arn : var.existing_tg_arn + type = "forward" + + forward { + + dynamic "target_group" { + for_each = local.deployments + content { + arn = each.value.target_group_arns[0] + weight = each.value.weight + } + } + + stickiness { + enabled = false + duration = 1 + } + } } tags = local.common_tags @@ -620,13 +649,15 @@ resource "aws_launch_template" "envoy" { # Auto Scaling Group # ========================================================================= module "asg" { + for_each = local.deployments + source = "terraform-aws-modules/autoscaling/aws" version = "~> 7.0" # Ensure S3 config files are uploaded before ASG starts - depends_on = [aws_s3_object.envoy_config_files] + depends_on = [aws_s3_object.envoy_config_files, aws_launch_template.envoy] - name = "${local.name_prefix}-asg" + name = "${local.name_prefix}-asg-v${each.key}" # ========================================================================= # Launch Template Configuration Strategy @@ -646,8 +677,8 @@ module "asg" { # Always use a launch template (either existing or our created one) # This prevents the ASG module from creating its own launch template create_launch_template = false - launch_template_id = local.launch_template_id - launch_template_version = local.launch_template_version + launch_template_id = each.value.lt_id + launch_template_version = each.value.lt_version # For mixed instances policy (spot enabled) mixed_instances_policy = var.enable_spot_instances ? { @@ -676,7 +707,7 @@ module "asg" { default_cooldown = 300 # Target groups - target_group_arns = [var.create_target_group ? aws_lb_target_group.envoy[0].arn : var.existing_tg_arn] + target_group_arns = each.value.target_group_arns # Termination and lifecycle termination_policies = var.termination_policies @@ -695,7 +726,7 @@ module "asg" { # Scaling policies - Created separately below # Tags - tags = local.common_tags + tags = merge(local.common_tags, { Deployment = each.value.deployment, Version = each.key }) } # ========================================================================= @@ -704,10 +735,10 @@ module "asg" { # CPU Target Tracking Scaling Policy resource "aws_autoscaling_policy" "cpu_target_tracking" { - count = var.enable_autoscaling && var.scaling_policies.cpu_target_tracking.enabled ? 1 : 0 + for_each = var.enable_autoscaling && var.scaling_policies.cpu_target_tracking.enabled ? local.deployments : {} name = "${local.name_prefix}-cpu-target-tracking" - autoscaling_group_name = module.asg.autoscaling_group_name + autoscaling_group_name = module.asg[each.key].autoscaling_group_name policy_type = "TargetTrackingScaling" target_tracking_configuration { @@ -720,10 +751,10 @@ resource "aws_autoscaling_policy" "cpu_target_tracking" { # Memory Target Tracking Scaling Policy resource "aws_autoscaling_policy" "memory_target_tracking" { - count = var.enable_autoscaling && var.scaling_policies.memory_target_tracking.enabled ? 1 : 0 + for_each = var.enable_autoscaling && var.scaling_policies.memory_target_tracking.enabled ? local.deployments : {} - name = "${local.name_prefix}-memory-target-tracking" - autoscaling_group_name = module.asg.autoscaling_group_name + name = "${local.name_prefix}-memory-target-tracking-${each.key}" + autoscaling_group_name = module.asg[each.key].autoscaling_group_name policy_type = "TargetTrackingScaling" target_tracking_configuration { @@ -736,7 +767,7 @@ resource "aws_autoscaling_policy" "memory_target_tracking" { metric_name = "mem_used_percent" dimensions { name = "AutoScalingGroupName" - value = module.asg.autoscaling_group_name + value = module.asg[each.key].autoscaling_group_name } } stat = "Average" diff --git a/terraform/aws/modules/composition/envoy-proxy/outputs.tf b/terraform/aws/modules/composition/envoy-proxy/outputs.tf index db0100f..bc6adfc 100644 --- a/terraform/aws/modules/composition/envoy-proxy/outputs.tf +++ b/terraform/aws/modules/composition/envoy-proxy/outputs.tf @@ -13,9 +13,9 @@ output "lb_zone_id" { value = var.create_lb ? module.alb[0].zone_id : null } -output "target_group_arn" { - description = "ARN of the target group" - value = var.create_target_group ? aws_lb_target_group.envoy[0].arn : var.existing_tg_arn +output "target_group_arns" { + description = "Map of deployment names to target group ARNs" + value = { for v in local.deployments : v.deployment => v.target_group_arns } } output "launch_template_id" { @@ -33,14 +33,18 @@ output "launch_template_created" { value = !var.use_existing_launch_template } -output "asg_id" { - description = "ID of the Auto Scaling Group" - value = module.asg.autoscaling_group_id +output "asg_ids" { + description = "Map of deployment names to Auto Scaling Group IDs" + value = { + for k, v in module.asg : local.deployments[k].deployment => v.autoscaling_group_id + } } -output "asg_name" { - description = "Name of the Auto Scaling Group" - value = module.asg.autoscaling_group_name +output "asg_names" { + description = "Map of deployment names to Auto Scaling Group names" + value = { + for k, v in module.asg : local.deployments[k].deployment => v.autoscaling_group_name + } } output "asg_security_group_id" { diff --git a/terraform/aws/modules/composition/envoy-proxy/variables.tf b/terraform/aws/modules/composition/envoy-proxy/variables.tf index 0329825..4fb4c01 100644 --- a/terraform/aws/modules/composition/envoy-proxy/variables.tf +++ b/terraform/aws/modules/composition/envoy-proxy/variables.tf @@ -37,10 +37,10 @@ variable "lb_ingress_rules" { from_port = number to_port = number protocol = string - cidr = optional(list(string)) # IPv4 CIDR blocks (e.g., ["0.0.0.0/0"]) - ipv6_cidr = optional(list(string)) # IPv6 CIDR blocks (e.g., ["::/0"]) - sg_id = optional(list(string)) # Security Group IDs - prefix_list_ids = optional(list(string)) # VPC Endpoint Prefix Lists (e.g., ["pl-6ea54007"]) + cidr = optional(list(string)) # IPv4 CIDR blocks (e.g., ["0.0.0.0/0"]) + ipv6_cidr = optional(list(string)) # IPv6 CIDR blocks (e.g., ["::/0"]) + sg_id = optional(list(string)) # Security Group IDs + prefix_list_ids = optional(list(string)) # VPC Endpoint Prefix Lists (e.g., ["pl-6ea54007"]) })) default = [] } @@ -52,10 +52,10 @@ variable "lb_egress_rules" { from_port = number to_port = number protocol = string - cidr = optional(list(string)) # IPv4 CIDR blocks (e.g., ["0.0.0.0/0"]) - ipv6_cidr = optional(list(string)) # IPv6 CIDR blocks (e.g., ["::/0"]) - sg_id = optional(list(string)) # Security Group IDs - prefix_list_ids = optional(list(string)) # VPC Endpoint Prefix Lists (e.g., ["pl-6ea54007"]) + cidr = optional(list(string)) # IPv4 CIDR blocks (e.g., ["0.0.0.0/0"]) + ipv6_cidr = optional(list(string)) # IPv6 CIDR blocks (e.g., ["::/0"]) + sg_id = optional(list(string)) # Security Group IDs + prefix_list_ids = optional(list(string)) # VPC Endpoint Prefix Lists (e.g., ["pl-6ea54007"]) })) default = [] } @@ -224,6 +224,15 @@ variable "existing_launch_template_version" { default = "$Latest" } +variable "blue_green_rollout" { + description = "Blue-green rollout configuration for Envoy" + type = object({ + blue_weight = number, + green_weight = number + }) + default = null +} + # ========================================================================= # Launch Template Advanced Configuration (ignored if use_existing_launch_template = true) # =========================================================================