-
Notifications
You must be signed in to change notification settings - Fork 0
/
ecs-restapi-service-stack.ts
134 lines (120 loc) · 6.16 KB
/
ecs-restapi-service-stack.ts
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
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import { Stack, StackProps, CfnOutput, Duration, Tags } from 'aws-cdk-lib';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as ssm from 'aws-cdk-lib/aws-ssm';
import { ApplicationLoadBalancer, ApplicationProtocol } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import { CLUSTER_NAME } from '../../ecs-ec2-cluster/lib/cluster-config';
import { SSM_PREFIX } from '../../ssm-prefix';
/**
*
*/
export class EcsRestAPIServiceStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const stage = this.node.tryGetContext('stage') || 'local';
const vpcId = this.node.tryGetContext('vpcId') || ssm.StringParameter.valueFromLookup(this, `${SSM_PREFIX}/vpc-id`);
const vpc = ec2.Vpc.fromLookup(this, 'vpc', { vpcId });
const clusterSgId = ssm.StringParameter.valueFromLookup(this, `${SSM_PREFIX}/cluster-securitygroup-id`);
const ecsSecurityGroup = ec2.SecurityGroup.fromSecurityGroupId(this, 'ecs-security-group', clusterSgId);
const cluster = ecs.Cluster.fromClusterAttributes(this, 'ecs-cluster', {
clusterName: `${CLUSTER_NAME}-${stage}`,
vpc,
securityGroups: [ecsSecurityGroup]
});
const serviceName = 'gpu-restapi'
const containerName = `${serviceName}-container`
const applicationPort = 8080;
const capacityProviderName = ssm.StringParameter.valueFromLookup(this, `${SSM_PREFIX}/cluster-capacityprovider-name`);
const executionRoleArn = ssm.StringParameter.valueFromLookup(this, `${SSM_PREFIX}/gpu-task-execution-role-arn`);
const taskRoleArn = ssm.StringParameter.valueFromLookup(this, `${SSM_PREFIX}/gpu-default-task-role-arn`);
const taskDefinition = new ecs.TaskDefinition(this, 'task-definition', {
compatibility: ecs.Compatibility.EC2,
family: `${serviceName}-task`,
executionRole: iam.Role.fromRoleArn(this, 'task-execution-role', cdk.Lazy.string({ produce: () => executionRoleArn })),
taskRole: iam.Role.fromRoleArn(this, 'task-role', cdk.Lazy.string({ produce: () => taskRoleArn }))
});
const container = taskDefinition.addContainer('container-restapi', {
containerName,
// image: ecs.ContainerImage.fromAsset(path.join(__dirname, "../../", "cpu-api")),
// image: ecs.ContainerImage.fromAsset(path.join(__dirname, "../../", "gpu-api")),
// or build with gpu-api/build.sh
image: ecs.ContainerImage.fromRegistry(`${props?.env?.account}.dkr.ecr.${props?.env?.region}.amazonaws.com/gpu-api:latest`),
gpuCount: 1,
cpu: 2048,
memoryReservationMiB: 2048
});
container.addPortMappings({ containerPort: applicationPort, hostPort: 0 });
const ecsService = new ecs.Ec2Service(this, 'ec2-service', {
cluster,
serviceName,
taskDefinition,
enableExecuteCommand: true,
capacityProviderStrategies: [{
capacityProvider: capacityProviderName,
weight: 1
}]
});
ecsService.autoScaleTaskCount({
minCapacity: 2,
maxCapacity: 20,
}).scaleOnCpuUtilization('cpuscaling', {
targetUtilizationPercent: 50,
scaleOutCooldown: Duration.seconds(60),
scaleInCooldown: Duration.seconds(120)
});
const logGroup = new logs.LogGroup(this, 'loggroup', {
logGroupName: serviceName,
removalPolicy: cdk.RemovalPolicy.DESTROY,
retention: logs.RetentionDays.TWO_WEEKS,
});
const albSecurityGroupName = `albsg-${serviceName}`
const albSecurityGroup = new ec2.SecurityGroup(this, albSecurityGroupName, {
securityGroupName: albSecurityGroupName,
vpc,
allowAllOutbound: true,
description: `ALB security group for ${serviceName} Service`
});
ecsSecurityGroup.addIngressRule(albSecurityGroup, ec2.Port.tcp(0), 'Allows all from ALB');
albSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow any')
Tags.of(ecsSecurityGroup).add('Stage', stage);
Tags.of(ecsSecurityGroup).add('Name', albSecurityGroupName);
const alb = new ApplicationLoadBalancer(this, 'alb', {
securityGroup: albSecurityGroup,
vpc,
loadBalancerName: `alb-${serviceName}`,
internetFacing: true,
deletionProtection: false,
idleTimeout: cdk.Duration.seconds(30),
});
alb.addListener('https-listener', {
protocol: ApplicationProtocol.HTTP,
open: false,
}).addTargets('ec2-service-target', {
targetGroupName: `tg-${serviceName}`,
port: applicationPort,
protocol: ApplicationProtocol.HTTP,
targets: [ecsService.loadBalancerTarget({
containerName: containerName,
containerPort: applicationPort,
})],
healthCheck: {
healthyThresholdCount: 2,
unhealthyThresholdCount: 5,
interval: Duration.seconds(12),
path: '/ping',
timeout: Duration.seconds(10),
},
deregistrationDelay: Duration.seconds(15)
});
(ecsService.node.defaultChild as ecs.CfnService).healthCheckGracePeriodSeconds = undefined;
new CfnOutput(this, 'Service', { value: ecsService.serviceArn });
new CfnOutput(this, 'TaskDefinition', { value: taskDefinition.family });
new CfnOutput(this, 'LogGroup', { value: logGroup.logGroupName });
new CfnOutput(this, 'ALB', { value: alb.loadBalancerDnsName });
new CfnOutput(this, 'TestURL', { value: `http://${alb.loadBalancerDnsName}/gputest` });
}
}