Support multiple public/private ALBs and NLBs per ECS cluster with per-service selection - #96
Conversation
…r-service selection Co-Authored-By: brandon <brandon@flightcontrol.dev>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Ravion Module Publish PlanDry run only. No Ravion API mutations were made.
Diffsrvn-ec2-service 1.0.0 -> 2.0.0--- remote
+++ compiled
- id: section_cluster_alb
label: Cluster load balancers
type: section
- - default: <<ref.stack.output.public_alb_http_listener_arn>>
+ - collapsible: true
+ default: <<ref.stack.output.public_albs_by_name>>
+ description: Public load balancer records from the selected cluster keyed by name, including listener ARNs and security group IDs.
+ id: public_albs_by_name
+ label: Public load balancers by name
+ required: false
+ type: object
+ - collapsible: true
+ default: <<ref.stack.output.private_albs_by_name>>
+ description: Private load balancer records from the selected cluster keyed by name, including listener ARNs and security group IDs.
+ id: private_albs_by_name
+ label: Private load balancers by name
+ required: false
+ type: object
+ - collapsible: true
+ default: <<ref.stack.output.public_alb_http_listener_arn>>
+ description: HTTP listener ARN from the cluster's first public ALB.
id: public_alb_http_listener_arn
immutable: true
label: Public ALB HTTP listener ARN
+ required: false
type: string
- collapsible: true
default: <<ref.stack.output.public_alb_https_listener_arn>>
- description: HTTPS listener ARN from the selected public ALB. Ravion uses it when present, otherwise falls back to HTTP.
+ description: HTTPS listener ARN from the cluster's first public ALB. Ravion uses it when present, otherwise falls back to HTTP.
id: public_alb_https_listener_arn
immutable: true
label: Public ALB HTTPS listener ARN
required: false
type: string
- - default: <<ref.stack.output.public_alb_security_group_id>>
+ - collapsible: true
+ default: <<ref.stack.output.public_alb_security_group_id>>
+ description: Security group ID of the cluster's first public ALB.
id: public_alb_security_group_id
immutable: true
label: Public ALB security group ID
+ required: false
type: string
- - default: <<ref.stack.output.private_alb_http_listener_arn>>
+ - collapsible: true
+ default: <<ref.stack.output.public_alb_arn_suffix>>
+ description: ARN suffix of the cluster's first public ALB for CloudWatch metrics.
+ id: public_alb_arn_suffix
+ immutable: true
+ label: Public ALB ARN suffix
+ required: false
+ type: string
+ - collapsible: true
+ default: <<ref.stack.output.private_alb_http_listener_arn>>
+ description: HTTP listener ARN from the cluster's first private ALB.
id: private_alb_http_listener_arn
immutable: true
label: Private ALB HTTP listener ARN
+ required: false
type: string
- collapsible: true
default: <<ref.stack.output.private_alb_https_listener_arn>>
- description: HTTPS listener ARN from the selected private ALB. Ravion uses it when present, otherwise falls back to HTTP.
+ description: HTTPS listener ARN from the cluster's first private ALB. Ravion uses it when present, otherwise falls back to HTTP.
id: private_alb_https_listener_arn
immutable: true
label: Private ALB HTTPS listener ARN
required: false
type: string
- - default: <<ref.stack.output.private_alb_security_group_id>>
+ - collapsible: true
+ default: <<ref.stack.output.private_alb_security_group_id>>
+ description: Security group ID of the cluster's first private ALB.
id: private_alb_security_group_id
immutable: true
label: Private ALB security group ID
- type: string
- - default: <<ref.stack.output.public_alb_arn_suffix>>
- id: public_alb_arn_suffix
- immutable: true
- label: Public ALB ARN suffix
+ required: false
type: string
- - default: <<ref.stack.output.private_alb_arn_suffix>>
+ - collapsible: true
+ default: <<ref.stack.output.private_alb_arn_suffix>>
+ description: ARN suffix of the cluster's first private ALB for CloudWatch metrics.
id: private_alb_arn_suffix
immutable: true
label: Private ALB ARN suffix
+ required: false
type: string
required: true
show_when:
@@
Ravion provisions the launch template, Auto Scaling Group, instance role and security group, SSM deploy document, app log group, and optional target group and listener rule. Web services can attach to a standalone Application Load Balancer or reuse the public or private ALB from an ECS cluster. The selected network and load balancer must use the same AWS account, region, and VPC. The Auto Scaling Group can still terminate instances because of scaling, health failures, or operator action. Root and data EBS volumes survive releases, but not instance termination.
- Terraform source: [flightcontrolhq/modules/compute/ec2_service](https://github.com/flightcontrolhq/modules/tree/rvn-ec2-service@1.0.0/compute/ec2_service)
+ Terraform source: [flightcontrolhq/modules/compute/ec2_service](https://github.com/flightcontrolhq/modules/tree/rvn-ec2-service@2.0.0/compute/ec2_service)
## Use cases
@@
base_path: compute/ec2_service
branch: main
execution_environment_id: << module.input.execution_environment_id >>
- ref: rvn-ec2-service@1.0.0
+ ref: rvn-ec2-service@2.0.0
repo: https://github.com/flightcontrolhq/modules
stack_id: <<stack.id>>
terraform_variables:rvn-ecs-cluster 1.0.0 -> 2.0.0--- remote
+++ compiled
not: ""
ec2_managed_scaling_enabled: true
type: number
- - description: Creates an internet-facing Application Load Balancer for services that should receive traffic from the public internet, such as websites, public APIs, and webhook endpoints.
- id: section_public_alb
- label: Public application load balancer
+ - description: Internet-facing Application Load Balancers for services that should receive traffic from the public internet, such as websites, public APIs, and webhook endpoints.
+ id: section_public_albs
+ label: Public application load balancers
type: section
- - default: true
- id: public_alb_enabled
- label: Public load balancer
- type: boolean
- - default: true
- description: Enable HTTPS listener on the public ALB.
- id: public_alb_https_enabled
- label: HTTPS
- show_when:
- public_alb_enabled: true
- type: boolean
- - description: Primary ACM certificate module for public ALB HTTPS.
- id: public_alb_certificate
- label: Certificate
- mapped_inputs:
+ - default:
+ - https_enabled: true
+ name: <<project.given_id>>-<<environment.given_id>>-pub
+ description: Each entry creates one internet-facing Application Load Balancer. Services on this cluster choose a load balancer by name.
+ id: public_albs
+ item_inputs:
+ - description: Unique load balancer name.
+ id: name
+ label: Name
+ patterns:
+ - message: The name must be 1-32 characters, contain only letters, numbers, and hyphens, and start and end with a letter or number.
+ pattern: ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,30}[a-zA-Z0-9])?$
+ required: true
+ type: string
+ - default: true
+ description: Enable an HTTPS listener on this load balancer.
+ id: https_enabled
+ label: HTTPS
+ type: boolean
+ - description: Primary ACM certificate module for HTTPS.
+ id: certificate
+ label: Certificate
+ mapped_inputs:
+ - add_button_label: Add certificate ARN
+ default:
+ - <<ref.stack.output.certificate_arn>>
+ description: ACM certificate ARNs resolved from the selected module. The first ARN is the default certificate.
+ id: certificate_arns
+ label: Certificate ARNs
+ placeholder: arn:aws:acm:...
+ required: true
+ type: string_array
+ required: true
+ show_when:
+ https_enabled: true
+ type: $ref:rvn-acm-certificate
- add_button_label: Add certificate ARN
- default:
- - <<ref.stack.output.certificate_arn>>
- description: ACM certificate ARNs resolved from the selected module. The first ARN is the default certificate.
- id: public_alb_certificate_arns
- label: Certificate ARNs
+ collapsible: true
+ default: []
+ description: Additional ACM certificate ARNs attached to the HTTPS listener for SNI.
+ id: additional_certificate_arns
+ label: Additional certificate ARNs
placeholder: arn:aws:acm:...
- required: true
+ show_when:
+ https_enabled: true
type: string_array
- required: true
- show_when:
- public_alb_enabled: true
- public_alb_https_enabled: true
- type: $ref:rvn-acm-certificate
- - add_button_label: Add certificate ARN
- collapsible: true
- default: []
- description: Additional ACM certificate ARNs attached to the public ALB HTTPS listener for SNI.
- id: public_alb_additional_certificate_arns
- label: Additional certificate ARNs
- placeholder: arn:aws:acm:...
- show_when:
- public_alb_enabled: true
- public_alb_https_enabled: true
- type: string_array
- - collapsible: true
- description: SSL policy for public ALB HTTPS.
- id: public_alb_ssl_policy
- label: SSL policy
- placeholder: ELBSecurityPolicy-TLS13-1-2-2021-06
- show_when:
- public_alb_enabled: true
- public_alb_https_enabled: true
- type: string
- - collapsible: true
- description: Idle timeout for the public ALB.
- id: public_alb_idle_timeout
- label: Idle timeout (seconds)
- max: 4000
- min: 1
- placeholder: "60"
- show_when:
- public_alb_enabled: true
- type: number
- - collapsible: true
- description: WAFv2 Web ACL ARN for the public ALB.
- id: public_alb_web_acl_arn
- label: WAF web ACL ARN
- placeholder: arn:aws:wafv2:...
- show_when:
- public_alb_enabled: true
- type: string
- - collapsible: true
- default: false
- description: Enable public ALB access logging.
- id: public_alb_access_logs_enabled
- label: Access logs
- show_when:
- public_alb_enabled: true
- type: boolean
- - collapsible: true
- description: Existing S3 bucket ARN for public ALB access logs.
- id: public_alb_access_logs_bucket_arn
- label: Access logs bucket ARN
- placeholder: arn:aws:s3:::my-bucket
- show_when:
- public_alb_access_logs_enabled: true
- public_alb_enabled: true
- type: string
- - description: Creates an internal Application Load Balancer for services that should only be reachable inside the VPC or private network, such as internal APIs, admin tools, and service-to-service traffic.
- id: section_private_alb
- label: Private application load balancer
+ - collapsible: true
+ description: SSL policy for the HTTPS listener.
+ id: ssl_policy
+ label: SSL policy
+ placeholder: ELBSecurityPolicy-TLS13-1-2-2021-06
+ show_when:
+ https_enabled: true
+ type: string
+ - add_button_label: Add CIDR block
+ collapsible: true
+ description: Allowed IPv4 CIDRs. Defaults to allowing all IPv4 traffic.
+ id: ingress_cidr_blocks
+ label: Ingress CIDRs
+ placeholder: 0.0.0.0/0
+ type: string_array
+ - add_button_label: Add IPv6 CIDR block
+ collapsible: true
+ description: Allowed IPv6 CIDRs. Defaults to allowing all IPv6 traffic.
+ id: ingress_ipv6_cidr_blocks
+ label: Ingress IPv6 CIDRs
+ placeholder: ::/0
+ type: string_array
+ - add_button_label: Add security group
+ collapsible: true
+ description: Security groups whose members can access this load balancer. Useful for sources without static CIDRs, such as CloudFront VPC origins.
+ id: ingress_security_group_ids
+ label: Ingress security groups
+ placeholder: sg-...
+ type: string_array
+ - collapsible: true
+ description: Idle timeout for this load balancer.
+ id: idle_timeout
+ label: Idle timeout (seconds)
+ max: 4000
+ min: 1
+ placeholder: "60"
+ type: number
+ - collapsible: true
+ description: WAFv2 Web ACL ARN for this load balancer.
+ id: web_acl_arn
+ label: WAF web ACL ARN
+ placeholder: arn:aws:wafv2:...
+ type: string
+ - collapsible: true
+ default: false
+ description: Enable access logging for this load balancer.
+ id: access_logs_enabled
+ label: Access logs
+ type: boolean
+ - collapsible: true
+ description: Existing S3 bucket ARN for access logs.
+ id: access_logs_bucket_arn
+ label: Access logs bucket ARN
+ placeholder: arn:aws:s3:::my-bucket
+ show_when:
+ access_logs_enabled: true
+ type: string
+ item_label: Public load balancer
+ item_title:
+ template: "{name}"
+ label: Public load balancers
+ type: object_array
+ - description: Internal Application Load Balancers for services that should only be reachable inside the VPC or private network, such as internal APIs, admin tools, and service-to-service traffic.
+ id: section_private_albs
+ label: Private application load balancers
type: section
- - default: false
- id: private_alb_enabled
- label: Private load balancer
- type: boolean
- - default: false
- description: Enable HTTPS listener on the private ALB.
- id: private_alb_https_enabled
- label: HTTPS
- show_when:
- private_alb_enabled: true
- type: boolean
- - description: Primary ACM certificate module for private ALB HTTPS.
- id: private_alb_certificate
- label: Certificate
- mapped_inputs:
+ - default: []
+ description: Each entry creates one internal Application Load Balancer. Services on this cluster choose a load balancer by name.
+ id: private_albs
+ item_inputs:
+ - description: Unique load balancer name.
+ id: name
+ label: Name
+ patterns:
+ - message: The name must be 1-32 characters, contain only letters, numbers, and hyphens, and start and end with a letter or number.
+ pattern: ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,30}[a-zA-Z0-9])?$
+ required: true
+ type: string
+ - default: false
+ description: Enable an HTTPS listener on this load balancer.
+ id: https_enabled
+ label: HTTPS
+ type: boolean
+ - description: Primary ACM certificate module for HTTPS.
+ id: certificate
+ label: Certificate
+ mapped_inputs:
+ - add_button_label: Add certificate ARN
+ default:
+ - <<ref.stack.output.certificate_arn>>
+ description: ACM certificate ARNs resolved from the selected module. The first ARN is the default certificate.
+ id: certificate_arns
+ label: Certificate ARNs
+ placeholder: arn:aws:acm:...
+ required: true
+ type: string_array
+ required: true
+ show_when:
+ https_enabled: true
+ type: $ref:rvn-acm-certificate
- add_button_label: Add certificate ARN
- default:
- - <<ref.stack.output.certificate_arn>>
- description: ACM certificate ARNs resolved from the selected module. The first ARN is the default certificate.
- id: private_alb_certificate_arns
- label: Certificate ARNs
+ collapsible: true
+ default: []
+ description: Additional ACM certificate ARNs attached to the HTTPS listener for SNI.
+ id: additional_certificate_arns
+ label: Additional certificate ARNs
placeholder: arn:aws:acm:...
- required: true
+ show_when:
+ https_enabled: true
type: string_array
- required: true
- show_when:
- private_alb_enabled: true
- private_alb_https_enabled: true
- type: $ref:rvn-acm-certificate
- - add_button_label: Add certificate ARN
- collapsible: true
- default: []
- description: Additional ACM certificate ARNs attached to the private ALB HTTPS listener for SNI.
- id: private_alb_additional_certificate_arns
- label: Additional certificate ARNs
- placeholder: arn:aws:acm:...
- show_when:
- private_alb_enabled: true
- private_alb_https_enabled: true
- type: string_array
- - collapsible: true
- description: SSL policy for private ALB HTTPS.
- id: private_alb_ssl_policy
- label: SSL policy
- placeholder: ELBSecurityPolicy-TLS13-1-2-2021-06
- show_when:
- private_alb_enabled: true
- private_alb_https_enabled: true
- type: string
- - add_button_label: Add CIDR block
- collapsible: true
- description: Allowed IPv4 CIDRs for private ALB access. Terraform defaults to RFC1918 private ranges.
- id: private_alb_ingress_cidr_blocks
- label: Ingress CIDRs
- placeholder: 10.0.0.0/8
- show_when:
- private_alb_enabled: true
- type: string_array
- - add_button_label: Add IPv6 CIDR block
- collapsible: true
- description: Allowed IPv6 CIDRs for private ALB access. Defaults to no IPv6 ingress.
- id: private_alb_ingress_ipv6_cidr_blocks
- label: Ingress IPv6 CIDRs
- placeholder: fd00::/8
- show_when:
- pri
... diff truncated ...rvn-ecs-nlb 1.0.0 -> 2.0.0--- remote
+++ compiled
- id: section_cluster_nlb
label: Cluster network load balancers
type: section
- - default: <<ref.stack.output.public_nlb_arn>>
+ - description: Which of the cluster's public network load balancers this service uses. Defaults to the first one when unset; a name that no longer exists on the cluster fails the deployment instead of silently using another load balancer.
+ id: public_nlb_name
+ label: Public NLB
+ required: false
+ show_when:
+ public_nlb_service_enabled: true
+ type: string
+ values: $values:ravion/module_input_options?module_type=rvn-ecs-cluster&module_instance_id=<<module.input.cluster>>&input=public_nlbs&value_field=name
+ - description: Which of the cluster's private network load balancers this service uses. Defaults to the first one when unset; a name that no longer exists on the cluster fails the deployment instead of silently using another load balancer.
+ id: private_nlb_name
+ label: Private NLB
+ required: false
+ show_when:
+ public_nlb_service_enabled: false
+ type: string
+ values: $values:ravion/module_input_options?module_type=rvn-ecs-cluster&module_instance_id=<<module.input.cluster>>&input=private_nlbs&value_field=name
+ - collapsible: true
+ default: <<ref.stack.output.public_nlbs_by_name>>
+ description: Public network load balancer records from the selected cluster keyed by name, including ARNs and security group IDs.
+ id: public_nlbs_by_name
+ label: Public NLBs by name
+ required: false
+ type: object
+ - collapsible: true
+ default: <<ref.stack.output.private_nlbs_by_name>>
+ description: Private network load balancer records from the selected cluster keyed by name, including ARNs and security group IDs.
+ id: private_nlbs_by_name
+ label: Private NLBs by name
+ required: false
+ type: object
+ - collapsible: true
+ default: <<ref.stack.output.public_nlb_arn>>
+ description: ARN of the cluster's first public NLB.
id: public_nlb_arn
immutable: true
label: Public NLB ARN
- type: string
- - default: <<ref.stack.output.public_nlb_arn_suffix>>
- id: public_nlb_arn_suffix
- immutable: true
- label: Public NLB ARN suffix
+ required: false
type: string
- - default: <<ref.stack.output.public_nlb_security_group_id>>
+ - collapsible: true
+ default: <<ref.stack.output.public_nlb_security_group_id>>
+ description: Security group ID of the cluster's first public NLB.
id: public_nlb_security_group_id
immutable: true
label: Public NLB security group ID
+ required: false
type: string
- - default: <<ref.stack.output.private_nlb_arn>>
- id: private_nlb_arn
+ - collapsible: true
+ default: <<ref.stack.output.public_nlb_arn_suffix>>
+ description: ARN suffix of the cluster's first public NLB for CloudWatch metrics.
+ id: public_nlb_arn_suffix
immutable: true
- label: Private NLB ARN
+ label: Public NLB ARN suffix
+ required: false
type: string
- - default: <<ref.stack.output.private_nlb_arn_suffix>>
- id: private_nlb_arn_suffix
+ - collapsible: true
+ default: <<ref.stack.output.private_nlb_arn>>
+ description: ARN of the cluster's first private NLB.
+ id: private_nlb_arn
immutable: true
- label: Private NLB ARN suffix
+ label: Private NLB ARN
+ required: false
type: string
- - default: <<ref.stack.output.private_nlb_security_group_id>>
+ - collapsible: true
+ default: <<ref.stack.output.private_nlb_security_group_id>>
+ description: Security group ID of the cluster's first private NLB.
id: private_nlb_security_group_id
immutable: true
label: Private NLB security group ID
+ required: false
type: string
+ - collapsible: true
+ default: <<ref.stack.output.private_nlb_arn_suffix>>
+ description: ARN suffix of the cluster's first private NLB for CloudWatch metrics.
+ id: private_nlb_arn_suffix
+ immutable: true
+ label: Private NLB ARN suffix
+ required: false
+ type: string
required: true
type: $ref:rvn-ecs-cluster
- id: section_service
@@
The module is intentionally focused on Layer 4 services behind a Network Load Balancer. Use ECS Web Service for HTTP host and path routing through an Application Load Balancer.
- Terraform source: [flightcontrolhq/modules/compute/ecs_service](https://github.com/flightcontrolhq/modules/tree/rvn-ecs-nlb@1.0.0/compute/ecs_service)
+ Terraform source: [flightcontrolhq/modules/compute/ecs_service](https://github.com/flightcontrolhq/modules/tree/rvn-ecs-nlb@2.0.0/compute/ecs_service)
## Use cases
@@
base_path: compute/ecs_service
branch: main
execution_environment_id: << module.input.execution_environment_id >>
- ref: rvn-ecs-nlb@1.0.0
+ ref: rvn-ecs-nlb@2.0.0
repo: https://github.com/flightcontrolhq/modules
stack_id: <<stack.id>>
terraform_variables:
@@
load_balancer_attachment:
container_port: << module.input.listeners[0].container_port >>
enabled: true
- nlb_listeners: '<< map(module.input.listeners, {"nlb_arn": (module.input.public_nlb_service_enabled ? module.input.public_nlb_arn : module.input.private_nlb_arn), "port": #.listener_port, "protocol": #.listener_protocol, "container_port": #.container_port, "target_protocol": (#.listener_protocol == "TLS" ? #.tls_target_protocol : #.listener_protocol), "certificate_arn": (#.listener_protocol == "TLS" ? #.tls_certificate_arn : nil), "ssl_policy": (#.listener_protocol == "TLS" ? #.tls_ssl_policy : nil), "alpn_policy": (#.listener_protocol == "TLS" && #.tls_alpn_policy != "None" ? #.tls_alpn_policy : nil)}) >>'
+ nlb_listeners: '<< map(module.input.listeners, {"nlb_arn": (module.input.public_nlb_service_enabled ? ((module.input.public_nlb_name || "") == "" ? module.input.public_nlb_arn : get(get(module.input.public_nlbs_by_name, module.input.public_nlb_name), "arn")) : ((module.input.private_nlb_name || "") == "" ? module.input.private_nlb_arn : get(get(module.input.private_nlbs_by_name, module.input.private_nlb_name), "arn"))), "port": #.listener_port, "protocol": #.listener_protocol, "container_port": #.container_port, "target_protocol": (#.listener_protocol == "TLS" ? #.tls_target_protocol : #.listener_protocol), "certificate_arn": (#.listener_protocol == "TLS" ? #.tls_certificate_arn : nil), "ssl_policy": (#.listener_protocol == "TLS" ? #.tls_ssl_policy : nil), "alpn_policy": (#.listener_protocol == "TLS" && #.tls_alpn_policy != "None" ? #.tls_alpn_policy : nil)}) >>'
target_group:
deregistration_delay: << module.input.deregistration_delay >>
health_check:
@@
type: source_ip
target_type: ip
load_balancer_ingress_cidr_blocks: "<< module.input.public_nlb_service_enabled ? module.input.load_balancer_ingress_cidr_blocks : module.input.private_load_balancer_ingress_cidr_blocks >>"
- load_balancer_security_group_id: "<< module.input.public_nlb_service_enabled ? module.input.public_nlb_security_group_id : module.input.private_nlb_security_group_id >>"
+ load_balancer_security_group_id: '<< module.input.public_nlb_service_enabled ? ((module.input.public_nlb_name || "") == "" ? module.input.public_nlb_security_group_id : get(get(module.input.public_nlbs_by_name, module.input.public_nlb_name), "security_group_id")) : ((module.input.private_nlb_name || "") == "" ? module.input.private_nlb_security_group_id : get(get(module.input.private_nlbs_by_name, module.input.private_nlb_name), "security_group_id")) >>'
name: << module.input.name >>
network_mode: awsvpc
new_deployment_forcing_enabled: << module.input.new_deployment_forcing_enabled >>
@@
source:
aws_account_id: << module.input.aws_account_id >>
dimensions:
- LoadBalancer: "<< module.input.public_nlb_service_enabled ? module.input.public_nlb_arn_suffix : module.input.private_nlb_arn_suffix >>"
+ LoadBalancer: '<< module.input.public_nlb_service_enabled ? ((module.input.public_nlb_name || "") == "" ? module.input.public_nlb_arn_suffix : get(get(module.input.public_nlbs_by_name, module.input.public_nlb_name), "arn_suffix")) : ((module.input.private_nlb_name || "") == "" ? module.input.private_nlb_arn_suffix : get(get(module.input.private_nlbs_by_name, module.input.private_nlb_name), "arn_suffix")) >>'
TargetGroup: << stack.output.target_group_arn_suffix >>
name: HealthyHostCount
namespace: AWS/NetworkELB
@@
source:
aws_account_id: << module.input.aws_account_id >>
dimensions:
- LoadBalancer: "<< module.input.public_nlb_service_enabled ? module.input.public_nlb_arn_suffix : module.input.private_nlb_arn_suffix >>"
+ LoadBalancer: '<< module.input.public_nlb_service_enabled ? ((module.input.public_nlb_name || "") == "" ? module.input.public_nlb_arn_suffix : get(get(module.input.public_nlbs_by_name, module.input.public_nlb_name), "arn_suffix")) : ((module.input.private_nlb_name || "") == "" ? module.input.private_nlb_arn_suffix : get(get(module.input.private_nlbs_by_name, module.input.private_nlb_name), "arn_suffix")) >>'
TargetGroup: << stack.output.target_group_arn_suffix >>
name: UnHealthyHostCount
namespace: AWS/NetworkELBrvn-ecs-web 1.0.0 -> 2.0.0--- remote
+++ compiled
- id: section_cluster_alb
label: Cluster load balancers
type: section
- - default: <<ref.stack.output.public_alb_http_listener_arn>>
+ - collapsible: true
+ default: <<ref.stack.output.public_albs_by_name>>
+ description: Public load balancer records from the selected cluster keyed by name, including listener ARNs and security group IDs.
+ id: public_albs_by_name
+ label: Public load balancers by name
+ required: false
+ type: object
+ - collapsible: true
+ default: <<ref.stack.output.private_albs_by_name>>
+ description: Private load balancer records from the selected cluster keyed by name, including listener ARNs and security group IDs.
+ id: private_albs_by_name
+ label: Private load balancers by name
+ required: false
+ type: object
+ - collapsible: true
+ default: <<ref.stack.output.public_alb_http_listener_arn>>
+ description: HTTP listener ARN from the cluster's first public ALB.
id: public_alb_http_listener_arn
immutable: true
label: Public ALB HTTP listener ARN
+ required: false
type: string
- collapsible: true
default: <<ref.stack.output.public_alb_https_listener_arn>>
- description: HTTPS listener ARN from the selected public ALB. Ravion uses it when present, otherwise falls back to HTTP.
+ description: HTTPS listener ARN from the cluster's first public ALB. Ravion uses it when present, otherwise falls back to HTTP.
id: public_alb_https_listener_arn
immutable: true
label: Public ALB HTTPS listener ARN
required: false
type: string
- - default: <<ref.stack.output.public_alb_security_group_id>>
+ - collapsible: true
+ default: <<ref.stack.output.public_alb_security_group_id>>
+ description: Security group ID of the cluster's first public ALB.
id: public_alb_security_group_id
immutable: true
label: Public ALB security group ID
+ required: false
type: string
- - default: <<ref.stack.output.private_alb_http_listener_arn>>
+ - collapsible: true
+ default: <<ref.stack.output.public_alb_arn_suffix>>
+ description: ARN suffix of the cluster's first public ALB for CloudWatch metrics.
+ id: public_alb_arn_suffix
+ immutable: true
+ label: Public ALB ARN suffix
+ required: false
+ type: string
+ - collapsible: true
+ default: <<ref.stack.output.private_alb_http_listener_arn>>
+ description: HTTP listener ARN from the cluster's first private ALB.
id: private_alb_http_listener_arn
immutable: true
label: Private ALB HTTP listener ARN
+ required: false
type: string
- collapsible: true
default: <<ref.stack.output.private_alb_https_listener_arn>>
- description: HTTPS listener ARN from the selected private ALB. Ravion uses it when present, otherwise falls back to HTTP.
+ description: HTTPS listener ARN from the cluster's first private ALB. Ravion uses it when present, otherwise falls back to HTTP.
id: private_alb_https_listener_arn
immutable: true
label: Private ALB HTTPS listener ARN
required: false
type: string
- - default: <<ref.stack.output.private_alb_security_group_id>>
+ - collapsible: true
+ default: <<ref.stack.output.private_alb_security_group_id>>
+ description: Security group ID of the cluster's first private ALB.
id: private_alb_security_group_id
immutable: true
label: Private ALB security group ID
- type: string
- - default: <<ref.stack.output.public_alb_arn_suffix>>
- id: public_alb_arn_suffix
- immutable: true
- label: Public ALB ARN suffix
+ required: false
type: string
- - default: <<ref.stack.output.private_alb_arn_suffix>>
+ - collapsible: true
+ default: <<ref.stack.output.private_alb_arn_suffix>>
+ description: ARN suffix of the cluster's first private ALB for CloudWatch metrics.
id: private_alb_arn_suffix
immutable: true
label: Private ALB ARN suffix
+ required: false
type: string
+ - description: Which of the cluster's public load balancers this service uses. Defaults to the first one when unset; a name that no longer exists on the cluster fails the deployment instead of silently using another load balancer.
+ id: public_alb_name
+ label: Public load balancer
+ required: false
+ show_when:
+ public_web_service_enabled: true
+ type: string
+ values: $values:ravion/module_input_options?module_type=rvn-ecs-cluster&module_instance_id=<<module.input.cluster>>&input=public_albs&value_field=name
+ - description: Which of the cluster's private load balancers this service uses. Defaults to the first one when unset; a name that no longer exists on the cluster fails the deployment instead of silently using another load balancer.
+ id: private_alb_name
+ label: Private load balancer
+ required: false
+ show_when:
+ public_web_service_enabled: false
+ type: string
+ values: $values:ravion/module_input_options?module_type=rvn-ecs-cluster&module_instance_id=<<module.input.cluster>>&input=private_albs&value_field=name
required: true
type: $ref:rvn-ecs-cluster
- id: section_service
@@
The module is intentionally focused on web services behind an Application Load Balancer. It uses the selected ECS cluster to inherit AWS account, region, VPC, subnets, capacity providers, load balancer listeners, and load balancer security groups.
- Terraform source: [flightcontrolhq/modules/compute/ecs_service](https://github.com/flightcontrolhq/modules/tree/rvn-ecs-web@1.0.0/compute/ecs_service)
+ Terraform source: [flightcontrolhq/modules/compute/ecs_service](https://github.com/flightcontrolhq/modules/tree/rvn-ecs-web@2.0.0/compute/ecs_service)
## Use cases
@@
base_path: compute/ecs_service
branch: main
execution_environment_id: << module.input.execution_environment_id >>
- ref: rvn-ecs-web@1.0.0
+ ref: rvn-ecs-web@2.0.0
repo: https://github.com/flightcontrolhq/modules
stack_id: <<stack.id>>
terraform_variables:
@@
- conditions:
- '...<< module.input.host_header_values != nil && module.input.host_header_values != [] ? [{type: "host-header", values: module.input.host_header_values}] : [] >>'
- '...<< module.input.path_pattern_values != nil && module.input.path_pattern_values != [] ? [{type: "path-pattern", values: module.input.path_pattern_values}] : module.input.host_header_values != nil && module.input.host_header_values != [] ? [] : [{type: "path-pattern", values: ["/*"]}] >>'
- listener_arn: "<< module.input.public_web_service_enabled ? (module.input.public_alb_https_listener_arn || module.input.public_alb_http_listener_arn) : (module.input.private_alb_https_listener_arn || module.input.private_alb_http_listener_arn) >>"
+ listener_arn: '<< module.input.public_web_service_enabled ? ((module.input.public_alb_name || "") == "" ? (module.input.public_alb_https_listener_arn || module.input.public_alb_http_listener_arn) : (get(get(module.input.public_albs_by_name, module.input.public_alb_name), "https_listener_arn") || get(get(module.input.public_albs_by_name, module.input.public_alb_name), "http_listener_arn"))) : ((module.input.private_alb_name || "") == "" ? (module.input.private_alb_https_listener_arn || module.input.private_alb_http_listener_arn) : (get(get(module.input.private_albs_by_name, module.input.private_alb_name), "https_listener_arn") || get(get(module.input.private_albs_by_name, module.input.private_alb_name), "http_listener_arn"))) >>'
priority: << module.input.listener_rule_priority >>
target_group:
health_check:
@@
enabled: << module.input.target_group_stickiness_enabled >>
type: '<< module.input.target_group_stickiness_enabled ? module.input.target_group_stickiness_type : "lb_cookie" >>'
target_type: ip
- load_balancer_security_group_id: "<< module.input.public_web_service_enabled ? module.input.public_alb_security_group_id : module.input.private_alb_security_group_id >>"
+ load_balancer_security_group_id: '<< module.input.public_web_service_enabled ? ((module.input.public_alb_name || "") == "" ? module.input.public_alb_security_group_id : get(get(module.input.public_albs_by_name, module.input.public_alb_name), "security_group_id")) : ((module.input.private_alb_name || "") == "" ? module.input.private_alb_security_group_id : get(get(module.input.private_albs_by_name, module.input.private_alb_name), "security_group_id")) >>'
name: << module.input.name >>
network_mode: awsvpc
new_deployment_forcing_enabled: << module.input.new_deployment_forcing_enabled >>
@@
source:
aws_account_id: << module.input.aws_account_id >>
dimensions:
- LoadBalancer: "<< module.input.public_web_service_enabled ? module.input.public_alb_arn_suffix : module.input.private_alb_arn_suffix >>"
+ LoadBalancer: '<< module.input.public_web_service_enabled ? ((module.input.public_alb_name || "") == "" ? module.input.public_alb_arn_suffix : get(get(module.input.public_albs_by_name, module.input.public_alb_name), "arn_suffix")) : ((module.input.private_alb_name || "") == "" ? module.input.private_alb_arn_suffix : get(get(module.input.private_albs_by_name, module.input.private_alb_name), "arn_suffix")) >>'
TargetGroup: << stack.output.target_group_arn_suffix >>
name: RequestCount
namespace: AWS/ApplicationELB
@@
source:
aws_account_id: << module.input.aws_account_id >>
dimensions:
- LoadBalancer: "<< module.input.public_web_service_enabled ? module.input.public_alb_arn_suffix : module.input.private_alb_arn_suffix >>"
+ LoadBalancer: '<< module.input.public_web_service_enabled ? ((module.input.public_alb_name || "") == "" ? module.input.public_alb_arn_suffix : get(get(module.input.public_albs_by_name, module.input.public_alb_name), "arn_suffix")) : ((module.input.private_alb_name || "") == "" ? module.input.private_alb_arn_suffix : get(get(module.input.private_albs_by_name, module.input.private_alb_name), "arn_suffix")) >>'
TargetGroup: << stack.output.target_group_arn_suffix >>
name: HTTPCode_Target_5XX_Count
namespace: AWS/ApplicationELB
@@
source:
aws_account_id: << module.input.aws_account_id >>
dimensions:
- LoadBalancer: "<< module.input.public_web_service_enabled ? module.input.public_alb_arn_suffix : module.input.private_alb_arn_suffix >>"
+ LoadBalancer: '<< module.input.public_web_service_enabled ? ((module.input.public_alb_name || "") == "" ? module.input.public_alb_arn_suffix : get(get(module.input.public_albs_by_name, module.input.public_alb_name), "arn_suffix")) : ((module.input.private_alb_name || "") == "" ? module.input.private_alb_arn_suffix : get(get(module.input.private_albs_by_name, module.input.private_alb_name), "arn_suffix")) >>'
TargetGroup: << stack.output.target_group_arn_suffix >>
name: HTTPCode_Target_4XX_Count
namespace: AWS/ApplicationELB
@@
source:
aws_account_id: << module.input.aws_account_id >>
dimensions:
- LoadBalancer: "<< module.input.public_web_service_enabled ? module.input.public_alb_arn_suffix : module.input.private_alb_arn_suffix >>"
+ LoadBalancer: '<< module.input.public_web_servic
... diff truncated ... |
Co-Authored-By: brandon <brandon@flightcontrol.dev>
| public_albs: >- | ||
| << map(module.input.public_albs != nil ? module.input.public_albs : [], {"name": #.name, | ||
| "https_enabled": #.https_enabled, "certificate_arns": ((#.certificate_arns != nil ? | ||
| #.certificate_arns : []) | concat(#.additional_certificate_arns != nil ? | ||
| #.additional_certificate_arns : [])), "ssl_policy": #.ssl_policy, "idle_timeout": | ||
| #.idle_timeout, "web_acl_arn": #.web_acl_arn, "access_logs_enabled": #.access_logs_enabled, | ||
| "access_logs_bucket_arn": #.access_logs_bucket_arn}) >> |
There was a problem hiding this comment.
Public ALB ingress controls are dropped
When a cluster requires a public ALB restricted by CIDRs or source security groups, this mapping omits all three ingress fields, so Terraform applies 0.0.0.0/0, ::/0, and no source security groups, leaving the listener internet-accessible instead of enforcing the intended restrictions.
How this was verified: The public mapping omits the fields that the corresponding private mapping forwards, and the Terraform object defaults them to unrestricted IPv4 and IPv6 ingress.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: compute/ecs_cluster/rvn-ecs-cluster-definition.yml
Line: 679-685
Comment:
**Public ALB ingress controls are dropped**
When a cluster requires a public ALB restricted by CIDRs or source security groups, this mapping omits all three ingress fields, so Terraform applies `0.0.0.0/0`, `::/0`, and no source security groups, leaving the listener internet-accessible instead of enforcing the intended restrictions.
**How this was verified:** The public mapping omits the fields that the corresponding private mapping forwards, and the Terraform object defaults them to unrestricted IPv4 and IPv6 ingress.
**Knowledge Base Used:**
- [ECS Cluster and ECS Service Modules](https://app.greptile.com/flightcontrol/-/custom-context/knowledge-base/flightcontrolhq/modules/-/docs/compute-ecs.md)
- [Terraform Stack module and shared partials](https://app.greptile.com/flightcontrol/-/custom-context/knowledge-base/flightcontrolhq/modules/-/docs/stack-and-partials.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| (concat(filter(module.input.public_albs, #.name == module.input.public_alb_name), | ||
| module.input.public_albs)[0].https_listener_arn || | ||
| concat(filter(module.input.public_albs, #.name == module.input.public_alb_name), | ||
| module.input.public_albs)[0].http_listener_arn) : | ||
| (concat(filter(module.input.private_albs, #.name == module.input.private_alb_name), | ||
| module.input.private_albs)[0].https_listener_arn || | ||
| concat(filter(module.input.private_albs, #.name == module.input.private_alb_name), | ||
| module.input.private_albs)[0].http_listener_arn) >> |
There was a problem hiding this comment.
Stale selectors bind another load balancer
When a selected load balancer is renamed or removed, filter returns an empty list and concat(..., module.input.public_albs)[0] silently selects the first remaining entry, causing the service listener, security group, and metric dimensions to point at a different ALB. The same fallback pattern also affects the NLB definition.
Knowledge Base Used: ECS Cluster and ECS Service Modules
Prompt To Fix With AI
This is a comment left during a code review.
Path: compute/ecs_service/rvn-ecs-web-definition.yml
Line: 344-351
Comment:
**Stale selectors bind another load balancer**
When a selected load balancer is renamed or removed, `filter` returns an empty list and `concat(..., module.input.public_albs)[0]` silently selects the first remaining entry, causing the service listener, security group, and metric dimensions to point at a different ALB. The same fallback pattern also affects the NLB definition.
**Knowledge Base Used:** [ECS Cluster and ECS Service Modules](https://app.greptile.com/flightcontrol/-/custom-context/knowledge-base/flightcontrolhq/modules/-/docs/compute-ecs.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.…blic ALB ingress mapping Co-Authored-By: brandon <brandon@flightcontrol.dev>
…nputs Co-Authored-By: brandon <brandon@flightcontrol.dev>
Summary
ECS clusters can now own arrays of load balancers instead of a single public/private ALB and NLB, and each ECS service selects which one it uses by name.
ecs_cluster Terraform (breaking, 2.0.0)
public_alb_*/private_alb_*/public_nlb_*/private_nlb_*singleton variables replaced by four object-array variables with per-item settings:cross_zone_load_balancing_enabled,security_group_ids, access logs, Elastic IPs.)count = length(var.<array>); unnamed items get deterministic names (<name>-pub,<name>-pub-2, ...).public_albs/private_albs/public_nlbs/private_nlbsrecord lists (arn, dns_name, arn_suffix, security_group_id, listener ARNs, ...), plus*_by_namemaps and*_optionsname lists. Pre-2.0 singleton outputs remain and return the first LB ornull(used by e.g. the CloudFront and EC2 service definitions).Definitions
rvn-ecs-cluster(2.0.0):public_albs/private_albs/public_nlbs/private_nlbsbecomeobject_arrayinputs with per-item HTTPS/cert/WAF/logging (ALB) and cross-zone/EIP/logging (NLB) settings; UI links iterate the output arrays.rvn-ecs-web/rvn-ecs-nlb(2.0.0): newpublic_alb_name/private_alb_name(and NLB equivalents) selector inputs under the cluster ref, populated via a new dynamic values source:$values:ravion/module_input_options?module_type=rvn-ecs-cluster&module_instance_id=<<module.input.cluster>>&input=public_albs&value_field=nameTerraform mappings pick the selected LB with
concat(filter(albs, #.name == selected_name), albs)[0].<field>— explicit selection wins, otherwise the first LB is used.<<ref.stack.output.public_albs>>etc.) while keeping the first-LB singleton fields forrvn-ec2-servicecompatibility.Requires flightcontrolhq/flightcontrol support for the
ravion/module_input_optionssource and for rendering selector mapped inputs under a selected ref (companion PR).Testing:
tofu validate, newtests/load_balancers.tftest.hcl(23 runs, multi-LB coverage) — full suite has one pre-existing failure onmain(ec2_spot_disabled_by_defaultasserts a nested module resource);ravion-modulestool tests updated and passing. Local dev publish was not run (no local dev API available in this environment).Link to Devin session: https://app.devin.ai/sessions/42785cd5cb8f4b9f8824f73682f6350d
Requested by: @flybayer
Greptile Summary
This PR changes ECS clusters from singleton load balancers to named ALB/NLB arrays and adds per-service selection.
Confidence Score: 1/5
The PR is not safe to merge until public ALB ingress settings are preserved, stale selectors are rejected, and named load balancers receive stable Terraform identities.
The current definition can unintentionally expose public ALBs, explicit service selections can silently bind to another load balancer after cluster edits, and positional module addressing can replace unrelated load balancers during normal array changes.
Files Needing Attention: compute/ecs_cluster/rvn-ecs-cluster-definition.yml, compute/ecs_cluster/load_balancers.tf, compute/ecs_service/rvn-ecs-web-definition.yml, compute/ecs_service/rvn-ecs-nlb-definition.yml
Security Review
The public ALB module-definition path drops all ingress restrictions and consequently provisions the Terraform defaults of unrestricted IPv4 and IPv6 ingress. The public object-array form must expose and forward the CIDR and source-security-group fields before release.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart LR C["ECS cluster definition"] --> A["Named ALB/NLB arrays"] A --> T["Terraform cluster module"] T --> O["Load-balancer record outputs"] O --> S["Per-service name selector"] S --> W["ECS web/NLB attachment"] W --> L["Selected listener, security group, and metrics"]Comments Outside Diff (1)
compute/ecs_cluster/load_balancers.tf, line 15-17 (link)When an ALB or NLB entry is inserted, removed, or reordered,
countshifts the Terraform addresses of subsequent entries even though their names are unchanged, causing Terraform to mutate or replace unrelated load balancers and potentially fail while deletion protection is enabled.Knowledge Base Used: ECS Cluster and ECS Service Modules
Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "Support multiple public/private ALBs and..." | Re-trigger Greptile
Context used: