export interface ApplicationLoadBalancerProps {
readonly internetFacing: boolean;
}
export interface NetworkProps {
readonly vpc: IVpc;
readonly subnets?: SubnetSelection;
}
export interface AiChatECSProps {
readonly networkProps: NetworkProps;
readonly region: string;
readonly accountId: string;
readonly cognitoInfo: any;
readonly accessKey?: string;
readonly secretAccessKey?: string;
readonly bucketName?: string;
readonly bucketArn?: string;
readonly partition: string;
}
export class EcsStack extends Construct {
readonly securityGroup: ISecurityGroup;
public ecsIP: string;
constructor(scope: Construct, id: string, props: AiChatECSProps) {
super(scope, id);
const repositoryName = "commonchatui-docker-repo";
// create ecr
const repository = new Repository(this, "CommonchatuiRepository", {
repositoryName: "commonchatui-docker-repo",
imageTagMutability: TagMutability.MUTABLE,
removalPolicy: RemovalPolicy.DESTROY,
});
// deploy front docker image
const ui_image = new DockerImageAsset(this, "commonchatui_image", {
directory: path.join(__dirname, "your_next_app_dic"), // 改成您前端应用的代码目录地址
file: "Dockerfile",
platform: Platform.LINUX_AMD64,
});
const imageTag = "latest";
const dockerImageUri = `${props.accountId}.dkr.ecr.${props.region}.amazonaws.com/${repositoryName}:${imageTag}`;
// upload front docker image to ecr
const ecrDeploy = new ECRDeployment(this, "commonchat_image_deploy", {
src: new DockerImageName(ui_image.imageUri),
dest: new DockerImageName(dockerImageUri),
});
new CfnOutput(this, "ECRRepositories", {
description: "ECR Repositories",
value: ecrDeploy.node.addr,
}).overrideLogicalId("ECRRepositories");
new CfnOutput(this, "ECRImageUrl", {
description: "ECR image url",
value: dockerImageUri,
}).overrideLogicalId("ECRImageUrl");
// create ecs security group
this.securityGroup = new SecurityGroup(this, "commonchat_sg", {
vpc: props.networkProps.vpc,
description: "Common chart security group",
allowAllOutbound: true,
});
// add inter rule
this.securityGroup.addIngressRule(
Peer.anyIpv4(),
Port.tcp(443),
"Default ui 443 port"
);
this.securityGroup.addIngressRule(
Peer.anyIpv4(),
Port.tcp(80),
"Default ui 80 port"
);
// add endpoint
props.networkProps.vpc.addInterfaceEndpoint("CommonChatVPCECREP", {
service: InterfaceVpcEndpointAwsService.ECR,
privateDnsEnabled: true,
securityGroups: [this.securityGroup],
});
props.networkProps.vpc.addInterfaceEndpoint("CommonChatVPCECRDockerEP", {
service: InterfaceVpcEndpointAwsService.ECR_DOCKER,
privateDnsEnabled: true,
securityGroups: [this.securityGroup],
});
props.networkProps.vpc.addInterfaceEndpoint("CommonChatVPCLogEP", {
service: InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS,
privateDnsEnabled: true,
securityGroups: [this.securityGroup],
});
props.networkProps.vpc.addGatewayEndpoint("CommonChatVPCS3", {
service: GatewayVpcEndpointAwsService.S3,
});
props.networkProps.vpc.addInterfaceEndpoint("CommonChatVPCLogECS", {
service: InterfaceVpcEndpointAwsService.ECS,
privateDnsEnabled: true,
securityGroups: [this.securityGroup],
});
props.networkProps.vpc.addInterfaceEndpoint("CommonChatVPCLogECSAgent", {
service: InterfaceVpcEndpointAwsService.ECS_AGENT,
privateDnsEnabled: true,
securityGroups: [this.securityGroup],
});
props.networkProps.vpc.addInterfaceEndpoint(
"CommonChatVPCLogECSTelemetry",
{
service: InterfaceVpcEndpointAwsService.ECS_TELEMETRY,
privateDnsEnabled: true,
securityGroups: [this.securityGroup],
}
);
// create ecr service
const ecsService = this.createECSGroup(props, imageTag, repository);
// create lb to ecs
this.createNlbEndpoint(props, ecsService, [80]);
}
private createECSGroup(
props: AiChatECSProps,
imageTag: string,
repository: IRepository
) {
const ecsClusterName = "CommonchatUiCluster";
const cluster = new Cluster(this, ecsClusterName, {
clusterName: "commonchat-ui-front",
vpc: props.networkProps.vpc,
enableFargateCapacityProviders: true,
});
const taskDefinition = new FargateTaskDefinition(
this,
"commonchatui_deploy",
{
cpu: 2048,
memoryLimitMiB: 4096,
runtimePlatform: {
operatingSystemFamily: OperatingSystemFamily.LINUX,
cpuArchitecture: CpuArchitecture.of("X86_64"),
},
family: "CommonchatuiDeployTask",
taskRole: this.getTaskRole(this, "CommonchatuiDeployTaskRole"),
executionRole: this.getExecutionTaskRole(
this,
"CommonchatuiDeployExecutionTaskRole"
),
}
);
const portMappings = [
{
containerPort: 80,
hostPort: 80,
protocol: Protocol.TCP,
appProtocol: AppProtocol.http,
name: "app_port",
},
{
containerPort: 443,
hostPort: 443,
protocol: Protocol.TCP,
appProtocol: AppProtocol.http,
name: "app_port_443",
},
];
const envConfig: any = {
DEFAULT_REGION: props.region,
BUCKET_NAME: props.bucketName,
USER_POOL_ID: props.cognitoInfo.userPoolId,
USER_POOL_CLIENT_ID: props.cognitoInfo.userPoolClientId,
};
if (props.accessKey && props.secretAccessKey) {
envConfig.ACCESS_KEY = props.accessKey;
envConfig.SECRET_ACCESS_KEY = props.secretAccessKey;
}
taskDefinition.addContainer("CommonchatuiContainer", {
containerName: "commonchatui_container",
image: ContainerImage.fromEcrRepository(repository, imageTag),
essential: true,
cpu: 2048,
memoryLimitMiB: 4096,
portMappings: portMappings,
environment: envConfig,
logging: LogDriver.awsLogs({
streamPrefix: "commonchat_ui",
}),
});
// 给ECS容器的角色配置权限
taskDefinition.addToTaskRolePolicy(
new PolicyStatement({
effect: Effect.ALLOW,
actions: [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream",
],
resources: ["*"],
})
);
return new FargateService(this, "CommonchatuiService", {
serviceName: "commonchat-ui-service",
cluster: cluster,
taskDefinition: taskDefinition,
desiredCount: 1,
assignPublicIp: false,
platformVersion: FargatePlatformVersion.LATEST,
securityGroups: [this.securityGroup],
vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS },
capacityProviderStrategies: [{ capacityProvider: "FARGATE", weight: 2 }],
propagateTags: PropagatedTagSource.TASK_DEFINITION,
maxHealthyPercent: 100,
minHealthyPercent: 0,
});
}
private getExecutionTaskRole(self: this, roleId: string): IRole {
// throw new Error('Method not implemented.');
return new Role(self, roleId, {
assumedBy: new ServicePrincipal("ecs-tasks.amazonaws.com"),
});
}
private getTaskRole(self: this, roleId: string): IRole {
return new Role(self, roleId, {
assumedBy: new ServicePrincipal("ecs-tasks.amazonaws.com"),
managedPolicies: [
ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AmazonECSTaskExecutionRolePolicy"
),
],
});
}
// 如果使用自有证书 选择ALB
private createNlbEndpoint(
props: AiChatECSProps,
ecsService: FargateService,
servicePorts: Array<number>
) {
const nlb = new NetworkLoadBalancer(this, "CommonchatUiLoadBalancer", {
loadBalancerName: "commonchat-ui-service",
internetFacing: true,
crossZoneEnabled: false,
vpc: props.networkProps.vpc,
vpcSubnets: { subnetType: SubnetType.PUBLIC } as SubnetSelection,
});
servicePorts.forEach((itemPort) => {
const listener = nlb.addListener(`CommonchatUiLBListener-${itemPort}`, {
port: itemPort,
protocol: LBProtocol.TCP_UDP,
});
const targetGroup = new NetworkTargetGroup(
this,
`CommonchatUiLBTargetGroup-${itemPort}`,
{
targetGroupName: "commonchat-ui-service-target",
vpc: props.networkProps.vpc,
port: itemPort,
protocol: LBProtocol.TCP_UDP,
targetType: TargetType.IP,
healthCheck: {
enabled: true,
interval: Duration.seconds(180),
healthyThresholdCount: 2,
unhealthyThresholdCount: 2,
port: itemPort.toString(),
protocol: LBProtocol.TCP,
timeout: Duration.seconds(10),
},
}
);
listener.addTargetGroups(
`CommonChatUiLBTargetGroups-${itemPort}`,
targetGroup
);
targetGroup.addTarget(
// targetGroups
ecsService.loadBalancerTarget({
containerName: "commonchattui_container",
containerPort: itemPort,
})
);
});
new CfnOutput(this, "FrontUiUrlDefault", {
description: "Common chart ui url by default",
value: `http://${nlb.loadBalancerDnsName}`, // alb
}).overrideLogicalId("FrontUiUrlDefault");
}
}