Fix cluster ASG signal failure, DNS duplication, type hacks, and health check default

- Remove duplicate shebang, set -e, and redundant SSM agent install from user data
  script so cfn-signal always runs (root cause of "0 SUCCESS signals" deploy failure)
- Remove DNS record creation from service stack's configureBlueGreenDns() to avoid
  CloudFormation conflicts with the persistent ALB stack that owns those records
- Replace readonly type assertion hacks with direct property assignments on 6 ALB/listener fields
- Change default health check path from /health to / for universal compatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 16:36:18 -08:00
parent fa1e865f50
commit 21f4fef6a3
2 changed files with 23 additions and 56 deletions

View File

@@ -230,22 +230,22 @@ export class SpicyEcsCluster extends Construct {
public readonly ec2CapacityProvider: ecs.AsgCapacityProvider;
/** External (internet-facing) load balancer */
public readonly externalLoadBalancer?: elbv2.ApplicationLoadBalancer;
public externalLoadBalancer?: elbv2.ApplicationLoadBalancer;
/** Internal load balancer */
public readonly internalLoadBalancer?: elbv2.ApplicationLoadBalancer;
public internalLoadBalancer?: elbv2.ApplicationLoadBalancer;
/** External HTTPS listener */
public readonly externalHttpsListener?: elbv2.ApplicationListener;
public externalHttpsListener?: elbv2.ApplicationListener;
/** External HTTP listener */
public readonly externalHttpListener?: elbv2.ApplicationListener;
public externalHttpListener?: elbv2.ApplicationListener;
/** Internal HTTPS listener */
public readonly internalHttpsListener?: elbv2.ApplicationListener;
public internalHttpsListener?: elbv2.ApplicationListener;
/** Internal HTTP listener */
public readonly internalHttpListener?: elbv2.ApplicationListener;
public internalHttpListener?: elbv2.ApplicationListener;
/** ECS Host security group */
public readonly ecsHostSecurityGroup: ec2.SecurityGroup;
@@ -374,21 +374,13 @@ export class SpicyEcsCluster extends Construct {
// User data script
const userData = ec2.UserData.forLinux();
userData.addCommands(
'#!/bin/bash',
'set -e',
'',
'# Install SSM agent and other utilities',
'yum install -y amazon-ssm-agent',
'systemctl enable amazon-ssm-agent',
'systemctl start amazon-ssm-agent',
'',
`# Configure ECS agent`,
'# Configure ECS agent',
`echo "ECS_CLUSTER=${stack.stackName}" >> /etc/ecs/ecs.config`,
'echo "ECS_ENABLE_SPOT_INSTANCE_DRAINING=true" >> /etc/ecs/ecs.config',
'echo "ECS_ENABLE_CONTAINER_METADATA=true" >> /etc/ecs/ecs.config',
'echo \'ECS_AVAILABLE_LOGGING_DRIVERS=["json-file","awslogs","splunk"]\' >> /etc/ecs/ecs.config',
'',
'# Signal success',
'# Signal CloudFormation (always runs, reports actual exit code)',
`/opt/aws/bin/cfn-signal -e $? --stack ${stack.stackName} --resource AutoScalingGroup --region ${region}`
);
@@ -722,8 +714,7 @@ def lambda_handler(event, context):
this.loadBalancerSecurityGroup!.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP');
this.loadBalancerSecurityGroup!.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS');
(this as { externalLoadBalancer: elbv2.ApplicationLoadBalancer }).externalLoadBalancer =
new elbv2.ApplicationLoadBalancer(this, 'ExternalLoadBalancer', {
this.externalLoadBalancer = new elbv2.ApplicationLoadBalancer(this, 'ExternalLoadBalancer', {
vpc: props.vpc,
internetFacing: true,
securityGroup: this.loadBalancerSecurityGroup,
@@ -737,8 +728,7 @@ def lambda_handler(event, context):
}
// HTTP Listener
(this as { externalHttpListener: elbv2.ApplicationListener }).externalHttpListener =
this.externalLoadBalancer!.addListener('ExternalHTTP', {
this.externalHttpListener = this.externalLoadBalancer!.addListener('ExternalHTTP', {
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
defaultAction: elbv2.ListenerAction.fixedResponse(404, {
@@ -749,8 +739,7 @@ def lambda_handler(event, context):
// HTTPS Listener (if certificate provided)
if (config.certificateArn) {
(this as { externalHttpsListener: elbv2.ApplicationListener }).externalHttpsListener =
this.externalLoadBalancer!.addListener('ExternalHTTPS', {
this.externalHttpsListener = this.externalLoadBalancer!.addListener('ExternalHTTPS', {
port: 443,
protocol: elbv2.ApplicationProtocol.HTTPS,
certificates: [elbv2.ListenerCertificate.fromArn(config.certificateArn)],
@@ -769,8 +758,7 @@ def lambda_handler(event, context):
props: SpicyEcsClusterProps,
config: { certificateArn?: string; idleTimeout: number; enableAccessLogs: boolean }
): void {
(this as { internalLoadBalancer: elbv2.ApplicationLoadBalancer }).internalLoadBalancer =
new elbv2.ApplicationLoadBalancer(this, 'InternalLoadBalancer', {
this.internalLoadBalancer = new elbv2.ApplicationLoadBalancer(this, 'InternalLoadBalancer', {
vpc: props.vpc,
internetFacing: false,
securityGroup: this.loadBalancerSecurityGroup,
@@ -784,8 +772,7 @@ def lambda_handler(event, context):
}
// HTTP Listener
(this as { internalHttpListener: elbv2.ApplicationListener }).internalHttpListener =
this.internalLoadBalancer!.addListener('InternalHTTP', {
this.internalHttpListener = this.internalLoadBalancer!.addListener('InternalHTTP', {
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
defaultAction: elbv2.ListenerAction.fixedResponse(404, {
@@ -796,8 +783,7 @@ def lambda_handler(event, context):
// HTTPS Listener (if certificate provided)
if (config.certificateArn) {
(this as { internalHttpsListener: elbv2.ApplicationListener }).internalHttpsListener =
this.internalLoadBalancer!.addListener('InternalHTTPS', {
this.internalHttpsListener = this.internalLoadBalancer!.addListener('InternalHTTPS', {
port: 443,
protocol: elbv2.ApplicationProtocol.HTTPS,
certificates: [elbv2.ListenerCertificate.fromArn(config.certificateArn)],