1
1
/* eslint-disable no-new */
2
2
import type { aws_certificatemanager as acm , aws_efs as efs , aws_lambda as lambda , aws_route53 as route53 } from 'aws-cdk-lib'
3
- import { Duration , CfnOutput as Output , aws_ec2 as ec2 , aws_ecs as ecs , aws_ecs_patterns as ecs_patterns , aws_secretsmanager as secretsmanager } from 'aws-cdk-lib'
3
+ import { Duration , CfnOutput as Output , RemovalPolicy , aws_ec2 as ec2 , aws_ecs as ecs , aws_secretsmanager as secretsmanager } from 'aws-cdk-lib'
4
4
import type { Construct } from 'constructs'
5
5
import { path as p } from '@stacksjs/path'
6
6
import { env } from '@stacksjs/env'
7
+ import { LogGroup } from 'aws-cdk-lib/aws-logs'
8
+ import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'
7
9
import type { NestedCloudProps } from '../types'
8
10
import type { EnvKey } from '../../../../env'
9
11
@@ -15,6 +17,7 @@ export interface ComputeStackProps extends NestedCloudProps {
15
17
}
16
18
17
19
export class ComputeStack {
20
+ lb : elbv2 . ApplicationLoadBalancer
18
21
apiServer : lambda . Function
19
22
apiServerUrl : lambda . FunctionUrl
20
23
@@ -30,35 +33,115 @@ export class ComputeStack {
30
33
vpc,
31
34
} )
32
35
33
- const taskDefinition = new ecs . FargateTaskDefinition ( scope , 'TaskDef' , {
36
+ const taskDefinition = new ecs . FargateTaskDefinition ( scope , 'TaskDefinition' , {
37
+ family : `${ props . appName } -${ props . appEnv } -api` ,
34
38
memoryLimitMiB : 512 , // Match your Lambda memory size
35
39
cpu : 256 , // Choose an appropriate value
40
+ runtimePlatform : {
41
+ cpuArchitecture : ecs . CpuArchitecture . ARM64 ,
42
+ } ,
36
43
} )
37
44
38
45
const container = taskDefinition . addContainer ( 'WebServerContainer' , {
46
+ containerName : `${ props . appName } -${ props . appEnv } -api` ,
39
47
image : ecs . ContainerImage . fromAsset ( p . frameworkPath ( 'server' ) ) ,
40
- portMappings : [ { containerPort : 80 } ] ,
48
+ logging : new ecs . AwsLogDriver ( {
49
+ streamPrefix : `${ props . appName } -${ props . appEnv } -web` ,
50
+ logGroup : new LogGroup ( scope , 'StacksApiLogs' , {
51
+ logGroupName : '/ecs/stacks-api' ,
52
+ removalPolicy : RemovalPolicy . DESTROY , // Automatically remove logs on stack deletion
53
+ } ) ,
54
+ } ) ,
55
+ healthCheck : {
56
+ command : [ 'CMD-SHELL' , 'curl -f http://localhost:3000/health || exit 1' ] ,
57
+ interval : Duration . seconds ( 10 ) ,
58
+ timeout : Duration . seconds ( 5 ) ,
59
+ retries : 3 ,
60
+ startPeriod : Duration . seconds ( 10 ) ,
61
+ } ,
62
+ } )
63
+
64
+ container . addPortMappings ( {
65
+ containerPort : 3000 ,
66
+ hostPort : 3000 ,
67
+ } )
68
+
69
+ const serviceSecurityGroup = new ec2 . SecurityGroup ( scope , 'ServiceSecurityGroup' , {
70
+ securityGroupName : `${ props . appName } -${ props . appEnv } -api-service-sg` ,
71
+ vpc,
72
+ description : 'Stacks Security Group for API Service' ,
73
+ } )
74
+
75
+ const publicLoadBalancerSG = new ec2 . SecurityGroup ( scope , 'PublicLoadBalancerSG' , {
76
+ securityGroupName : `${ props . appName } -${ props . appEnv } -public-load-balancer-sg` ,
77
+ vpc,
78
+ description : 'Access to the public facing load balancer' ,
79
+ } )
80
+
81
+ // Assuming serviceSecurityGroup and publicLoadBalancerSG are already defined
82
+ serviceSecurityGroup . addIngressRule (
83
+ publicLoadBalancerSG ,
84
+ ec2 . Port . allTraffic ( ) ,
85
+ 'Ingress from the public ALB' ,
86
+ )
87
+
88
+ // Assuming serviceSecurityGroup and publicLoadBalancerSG are already defined
89
+ serviceSecurityGroup . addIngressRule ( publicLoadBalancerSG , ec2 . Port . allTraffic ( ) , 'Ingress from the public ALB' )
90
+
91
+ this . lb = new elbv2 . ApplicationLoadBalancer ( scope , 'ApplicationLoadBalancer' , {
92
+ loadBalancerName : `${ props . appName } -${ props . appEnv } -alb` ,
93
+ vpc,
94
+ vpcSubnets : {
95
+ subnets : vpc . selectSubnets ( {
96
+ subnetType : ec2 . SubnetType . PUBLIC ,
97
+ onePerAz : true ,
98
+ } ) . subnets ,
99
+ } ,
100
+ internetFacing : true ,
101
+ idleTimeout : Duration . seconds ( 30 ) ,
102
+ securityGroup : publicLoadBalancerSG ,
103
+ } )
104
+
105
+ const serviceTargetGroup = new elbv2 . ApplicationTargetGroup ( scope , 'ServiceTargetGroup' , {
106
+ // targetGroupName: `${props.appName}-${props.appEnv}-service-tg`,
107
+ vpc,
108
+ targetType : elbv2 . TargetType . IP ,
109
+ protocol : elbv2 . ApplicationProtocol . HTTP ,
110
+ port : 3000 ,
111
+ healthCheck : {
112
+ interval : Duration . seconds ( 6 ) ,
113
+ path : '/' ,
114
+ protocol : elbv2 . Protocol . HTTP ,
115
+ timeout : Duration . seconds ( 5 ) ,
116
+ healthyThresholdCount : 2 ,
117
+ unhealthyThresholdCount : 10 ,
118
+ } ,
41
119
} )
42
120
43
- const fargateService = new ecs_patterns . ApplicationLoadBalancedFargateService ( scope , 'FargateService ' , {
44
- serviceName : `${ props . slug } -${ props . appEnv } -fargate -service` ,
121
+ const service = new ecs . FargateService ( scope , 'StacksApiService ' , {
122
+ serviceName : `${ props . appName } -${ props . appEnv } -api -service` ,
45
123
cluster,
46
124
taskDefinition,
47
- desiredCount : 1 , // Start with 1 task instance
48
- // Other configurations like public load balancer, domain name, etc.
49
- publicLoadBalancer : true ,
50
- taskSubnets : vpc . selectSubnets ( { subnetType : ec2 . SubnetType . PUBLIC , onePerAz : true } ) ,
125
+ desiredCount : 2 ,
51
126
assignPublicIp : true ,
52
- listenerPort : 80 ,
127
+ maxHealthyPercent : 200 ,
128
+ vpcSubnets : vpc . selectSubnets ( {
129
+ subnetType : ec2 . SubnetType . PUBLIC ,
130
+ onePerAz : true ,
131
+ } ) ,
132
+ minHealthyPercent : 75 ,
133
+ securityGroups : [ serviceSecurityGroup ] ,
53
134
} )
54
135
55
- fargateService . targetGroup . configureHealthCheck ( {
56
- path : '/health' ,
57
- interval : Duration . seconds ( 60 ) ,
136
+ service . attachToApplicationTargetGroup ( serviceTargetGroup )
137
+ publicLoadBalancerSG . addIngressRule ( ec2 . Peer . anyIpv4 ( ) , ec2 . Port . allTraffic ( ) )
138
+
139
+ this . lb . addListener ( 'HttpListener' , {
140
+ port : 80 ,
141
+ defaultAction : elbv2 . ListenerAction . forward ( [ serviceTargetGroup ] ) ,
58
142
} )
59
143
60
- // ec2.Peer.securityGroupId('YOUR_SECURITY_GROUP_ID'
61
- props . fileSystem . connections . allowFromAnyIpv4 ( ec2 . Port . tcp ( 2049 ) )
144
+ props . fileSystem . connections . allowFromAnyIpv4 ( ec2 . Port . tcp ( 2049 ) ) // port 2049 (NFS) for EFS
62
145
63
146
const volumeName = `${ props . slug } -${ props . appEnv } -efs`
64
147
taskDefinition . addVolume ( {
@@ -74,6 +157,22 @@ export class ComputeStack {
74
157
readOnly : false ,
75
158
} )
76
159
160
+ // Setup AutoScaling policy
161
+ // TODO: make this configurable in cloud.compute
162
+ const scaling = service . autoScaleTaskCount ( { maxCapacity : 2 } )
163
+
164
+ scaling . scaleOnCpuUtilization ( 'CpuScaling' , {
165
+ targetUtilizationPercent : 50 ,
166
+ scaleInCooldown : Duration . seconds ( 60 ) ,
167
+ scaleOutCooldown : Duration . seconds ( 60 ) ,
168
+ } )
169
+
170
+ scaling . scaleOnMemoryUtilization ( 'MemoryScaling' , {
171
+ targetUtilizationPercent : 60 ,
172
+ scaleInCooldown : Duration . seconds ( 60 ) ,
173
+ scaleOutCooldown : Duration . seconds ( 60 ) ,
174
+ } )
175
+
77
176
const keysToRemove = [ '_HANDLER' , '_X_AMZN_TRACE_ID' , 'AWS_REGION' , 'AWS_EXECUTION_ENV' , 'AWS_LAMBDA_FUNCTION_NAME' , 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' , 'AWS_LAMBDA_FUNCTION_VERSION' , 'AWS_LAMBDA_INITIALIZATION_TYPE' , 'AWS_LAMBDA_LOG_GROUP_NAME' , 'AWS_LAMBDA_LOG_STREAM_NAME' , 'AWS_ACCESS_KEY' , 'AWS_ACCESS_KEY_ID' , 'AWS_SECRET_ACCESS_KEY' , 'AWS_SESSION_TOKEN' , 'AWS_LAMBDA_RUNTIME_API' , 'LAMBDA_TASK_ROOT' , 'LAMBDA_RUNTIME_DIR' , '_' ]
78
177
keysToRemove . forEach ( key => delete env [ key as EnvKey ] )
79
178
@@ -86,7 +185,7 @@ export class ComputeStack {
86
185
} ,
87
186
} )
88
187
89
- secrets . grantRead ( fargateService . taskDefinition . executionRole ! )
188
+ secrets . grantRead ( service . taskDefinition . executionRole ! )
90
189
container . addEnvironment ( 'SECRETS_ARN' , secrets . secretArn )
91
190
92
191
const apiPrefix = 'api'
@@ -96,8 +195,8 @@ export class ComputeStack {
96
195
} )
97
196
98
197
new Output ( scope , 'ApiVanityUrl' , {
99
- value : '' ,
100
- description : 'The Vanity URL of the deployed application ' ,
198
+ value : `http:// ${ this . lb . loadBalancerDnsName } ` ,
199
+ description : 'The Vanity URL / DNS name of the load balancer ' ,
101
200
} )
102
201
}
103
202
}
0 commit comments