Enterprise-Grade Blue-Green Deployments at Scale: Architecture, Automated Verification, and Fallback Strategy Using AWS CDK v2, ECS Fargate, and CodeDeploy
For enterprise systems, an application upgrade cannot risk localized outages or regional downtime. Relying on simple rolling updates frequently introduces issues during the transition window: transient 502/504 errors, container startup delays, and problematic rollbacks if a structural bug passes the staging environment. This comprehensive technical guide details the implementation of an immutable, automated Blue-Green Deployment Infrastructure running on Amazon ECS Fargate.
1. Architecture Design Topology
The core component of this architecture is the complete decoupling of the active production traffic path from the pre-production validation path via a single Application Load Balancer mapping to separate, distinct target groups.
[ Public Internet ]
│
[ AWS WAF v2 ACL ]
│
[ Application Load Balancer ]
├── Port 80 (Prod) ──► [ Target Group Blue ] ──► Fargate Tasks v1.0
└── Port 8080 (Test) ──► [ Target Group Green ] ──► Fargate Tasks v2.0
│
[ Lifecycle Hooks ]
│
Lambda (Integration Tests)
│
[ AWS WAF v2 ACL ]
│
[ Application Load Balancer ]
├── Port 80 (Prod) ──► [ Target Group Blue ] ──► Fargate Tasks v1.0
└── Port 8080 (Test) ──► [ Target Group Green ] ──► Fargate Tasks v2.0
│
[ Lifecycle Hooks ]
│
Lambda (Integration Tests)
During a deployment lifecycle, AWS CodeDeploy dynamically modifies the internal routing rules of the Application Load Balancer listener. The new application version is provisioned within the Green environment and subjected to active integration tests while production workloads remain isolated on the Blue tasks.
2. Infrastructure as Code: AWS CDK TypeScript Blueprint
The following implementation configures the strict infrastructure blueprint. Save this as
ecs-blue-green-stack.ts within your CDK code repository.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as codedeploy from 'aws-cdk-lib/aws-codedeploy';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';
export interface EcsBlueGreenStackProps extends cdk.StackProps {
readonly environment: string;
readonly ecrRepositoryArn: string;
}
export class EcsBlueGreenStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: EcsBlueGreenStackProps) {
super(scope, id, props);
// 1. Enterprise Network Topology
const vpc = new ec2.Vpc(this, 'EnterpriseVpc', {
maxAzs: 3,
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
subnetConfiguration: [
{
cidrMask: 24,
name: 'IngressPublic',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 22,
name: 'ApplicationPrivate',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
{
cidrMask: 24,
name: 'DataIsolated',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
}
],
});
// 2. Compute Container Cluster Configuration
const cluster = new ecs.Cluster(this, 'ProductionCluster', {
vpc,
clusterName: `${props.environment}-core-compute`,
containerInsights: true,
});
// 3. Application Load Balancer & Security Posture
const albSecurityGroup = new ec2.SecurityGroup(this, 'AlbSecurityGroup', {
vpc,
description: 'Restrict access to load balancer entry points',
allowAllOutbound: true,
});
albSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow production HTTP traffic');
albSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(8080), 'Allow developer/pipeline test traffic');
const alb = new elbv2.ApplicationLoadBalancer(this, 'PublicAlb', {
vpc,
internetFacing: true,
securityGroup: albSecurityGroup,
loadBalancerName: `${props.environment}-edge-alb`,
});
// 4. Target Group Structuring for Routing Topologies
const targetGroupBlue = new elbv2.ApplicationTargetGroup(this, 'TargetGroupBlue', {
vpc,
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
targetType: elbv2.TargetType.IP,
deregistrationDelay: cdk.Duration.seconds(30),
healthCheck: {
path: '/healthz',
healthyThresholdCount: 2,
unhealthyThresholdCount: 3,
interval: cdk.Duration.seconds(15),
timeout: cdk.Duration.seconds(5),
},
});
const targetGroupGreen = new elbv2.ApplicationTargetGroup(this, 'TargetGroupGreen', {
vpc,
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
targetType: elbv2.TargetType.IP,
deregistrationDelay: cdk.Duration.seconds(30),
healthCheck: {
path: '/healthz',
healthyThresholdCount: 2,
unhealthyThresholdCount: 3,
interval: cdk.Duration.seconds(15),
timeout: cdk.Duration.seconds(5),
},
});
// 5. Ingress Traffic Routers (Listeners)
const productionListener = alb.addListener('ProductionTrafficListener', {
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
defaultTargetGroups: [targetGroupBlue],
});
const testingListener = alb.addListener('TestingTrafficListener', {
port: 8080,
protocol: elbv2.ApplicationProtocol.HTTP,
defaultTargetGroups: [targetGroupGreen],
});
// 6. Logical Task Definitions & Container Execution IAM Roles
const taskExecutionRole = new iam.Role(this, 'TaskExecutionRole', {
assumedBy: new iam.ServicePrincipal('://amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy'),
],
});
const fargateTaskDefinition = new ecs.FargateTaskDefinition(this, 'ApplicationTaskDef', {
memoryLimitMiB: 2048,
cpu: 512,
executionRole: taskExecutionRole,
});
const webContainer = fargateTaskDefinition.addContainer('ApplicationContainer', {
image: ecs.ContainerImage.fromRegistry('nginxdemos/hello:plain-text'),
logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'AppServer' }),
environment: {
DEPLOY_ENV: props.environment,
},
});
webContainer.addPortMappings({ containerPort: 80 });
// 7. Base Fargate Service Definition
const fargateService = new ecs.FargateService(this, 'CoreFargateService', {
cluster,
taskDefinition: fargateTaskDefinition,
desiredCount: 3,
deploymentController: {
type: ecs.DeploymentControllerType.CODE_DEPLOY,
},
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
});
// Explicitly link the primary target group to the container
fargateService.attachToApplicationTargetGroup(targetGroupBlue);
// 8. Resiliency Monitoring: Telemetry-Driven Metrics & Automated Alarms
const alb5xxAlarm = new cloudwatch.Alarm(this, 'AlbHighErrorRateAlarm', {
metric: targetGroupBlue.metricHttpCodeTarget(elbv2.HttpCodeTarget.TARGET_5XX_COUNT, {
period: cdk.Duration.minutes(1),
statistic: 'Sum',
}),
threshold: 5,
evaluationPeriods: 1,
datapointsToAlarm: 1,
alarmDescription: 'Triggers active deployment rollback if target application 5XX errors cross acceptable bounds.',
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
});
// 9. Automated Lifecycle Deployment Configuration via CodeDeploy
const integrationTestLambda = new lambda.Function(this, 'DeploymentValidationHook', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromInline(`
const { CodeDeployClient, PutLifecycleEventHookExecutionStatusCommand } = require("@aws-sdk/client-codedeploy");
const client = new CodeDeployClient();
exports.handler = async (event) => {
console.log("Received Execution Event Context:", JSON.stringify(event, null, 2));
const deploymentId = event.DeploymentId;
const lifecycleEventHookExecutionId = event.LifecycleEventHookExecutionId;
let status = "Succeeded";
// Custom testing or validation validation loop here
const command = new PutLifecycleEventHookExecutionStatusCommand({
deploymentId,
lifecycleEventHookExecutionId,
status
});
await client.send(command);
return { statusCode: 200 };
};
`),
});
integrationTestLambda.addToRolePolicy(new iam.PolicyStatement({
actions: ['codedeploy:PutLifecycleEventHookExecutionStatus'],
resources: ['*'],
}));
new codedeploy.EcsDeploymentGroup(this, 'EcsOrchestrationDeploymentGroup', {
service: fargateService,
blueGreenDeploymentConfig: {
blueTargetGroup,
greenTargetGroup,
listener: productionListener,
testListener: testingListener,
},
deploymentConfig: codedeploy.EcsDeploymentConfig.CANARY_10PERCENT_5MINUTES,
alarms: [alb5xxAlarm],
});
}
}3. The Orchestration Manifest: appspec.yaml
To bridge runtime deployments, AWS CodeDeploy evaluates an immutable configuration schema within the project source root. This defines the container replacement specifications and runtime verification logic.
1
2
3
4
5
6
7
8
9
10
11
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: "<TASK_DEFINITION_ARN>"
LoadBalancerInfo:
ContainerName: "ApplicationContainer"
ContainerPort: 80
Hooks:
- BeforeAllowTraffic: "arn:aws:lambda:us-east-1:123456789012:function:EcsBlueGreenStack-DeploymentValidationHook"4. Operational Guardrails & Data Architecture Patterns
When adopting automated canary routing across an enterprise, there are several key architecture patterns to keep in mind:
The "Expand and Contract" Schema Lifecycle
Blue-Green routing updates only compute tasks, not data storage systems. If a new deployment includes structural database schema modifications (e.g., PostgreSQL or Amazon DynamoDB), breaking changes will trigger instant outages across active nodes.
- Phase 1 (Expand): Deploy database migrations before running the application pipeline. Modifications must be completely additive (e.g., creating a new table or adding a nullable column). Both the old and new application versions must be able to run against this shared database state concurrently.
- Phase 2 (Compute Update): Trigger the Fargate Blue-Green deployment pipeline to shift user sessions over to the new codebase.
- Phase 3 (Contract): Once the deployment succeeds and the old environment is torn down, run a secondary migration script to clean up deprecated columns, constraints, or outdated data models.
Connection Draining Tuning
By default, Target Group connection draining values are set to 300 seconds. In high-velocity microservices architectures, waiting 5 minutes for old tasks to stop can introduce delays into delivery windows. For RESTful JSON APIs processing short-lived transactions, reduce the target group's deregistration delay down to 15 to 30 seconds. This speeds up container teardown operations without dropping active TCP connections.
#devops #aws-cdk #ecs-fargate #codedeploy #blue-green #enterprise-infrastructure
#devops #aws-cdk #ecs-fargate #codedeploy #blue-green #enterprise-infrastructure
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article