add cdk scripts
This commit is contained in:
parent
384fb84950
commit
d9bae37ccc
17 changed files with 5362 additions and 0 deletions
9
deploy/scripts/cdk/.gitignore
vendored
Normal file
9
deploy/scripts/cdk/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
*.js
|
||||||
|
!jest.config.js
|
||||||
|
*.d.ts
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# CDK asset staging directory
|
||||||
|
.cdk.staging
|
||||||
|
cdk.out
|
||||||
|
!/lib
|
||||||
6
deploy/scripts/cdk/.npmignore
Normal file
6
deploy/scripts/cdk/.npmignore
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
*.ts
|
||||||
|
!*.d.ts
|
||||||
|
|
||||||
|
# CDK asset staging directory
|
||||||
|
.cdk.staging
|
||||||
|
cdk.out
|
||||||
14
deploy/scripts/cdk/README.md
Normal file
14
deploy/scripts/cdk/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Welcome to your CDK TypeScript project
|
||||||
|
|
||||||
|
This is a blank project for CDK development with TypeScript.
|
||||||
|
|
||||||
|
The `cdk.json` file tells the CDK Toolkit how to execute your app.
|
||||||
|
|
||||||
|
## Useful commands
|
||||||
|
|
||||||
|
* `npm run build` compile typescript to js
|
||||||
|
* `npm run watch` watch for changes and compile
|
||||||
|
* `npm run test` perform the jest unit tests
|
||||||
|
* `cdk deploy` deploy this stack to your default AWS account/region
|
||||||
|
* `cdk diff` compare deployed stack with current state
|
||||||
|
* `cdk synth` emits the synthesized CloudFormation template
|
||||||
21
deploy/scripts/cdk/bin/cdk.ts
Normal file
21
deploy/scripts/cdk/bin/cdk.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
import 'source-map-support/register';
|
||||||
|
import * as cdk from 'aws-cdk-lib';
|
||||||
|
import { LangflowAppStack } from '../lib/cdk-stack';
|
||||||
|
|
||||||
|
const app = new cdk.App();
|
||||||
|
new LangflowAppStack(app, 'LangflowAppStack', {
|
||||||
|
/* If you don't specify 'env', this stack will be environment-agnostic.
|
||||||
|
* Account/Region-dependent features and context lookups will not work,
|
||||||
|
* but a single synthesized template can be deployed anywhere. */
|
||||||
|
|
||||||
|
/* Uncomment the next line to specialize this stack for the AWS Account
|
||||||
|
* and Region that are implied by the current CLI configuration. */
|
||||||
|
// env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
|
||||||
|
|
||||||
|
/* Uncomment the next line if you know exactly what Account and Region you
|
||||||
|
* want to deploy the stack to. */
|
||||||
|
// env: { account: '123456789012', region: 'us-east-1' },
|
||||||
|
|
||||||
|
/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
|
||||||
|
});
|
||||||
55
deploy/scripts/cdk/cdk.json
Normal file
55
deploy/scripts/cdk/cdk.json
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
"app": "npx ts-node --prefer-ts-exts bin/cdk.ts",
|
||||||
|
"watch": {
|
||||||
|
"include": [
|
||||||
|
"**"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"README.md",
|
||||||
|
"cdk*.json",
|
||||||
|
"**/*.d.ts",
|
||||||
|
"**/*.js",
|
||||||
|
"tsconfig.json",
|
||||||
|
"package*.json",
|
||||||
|
"yarn.lock",
|
||||||
|
"node_modules",
|
||||||
|
"test"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
|
||||||
|
"@aws-cdk/core:checkSecretUsage": true,
|
||||||
|
"@aws-cdk/core:target-partitions": [
|
||||||
|
"aws",
|
||||||
|
"aws-cn"
|
||||||
|
],
|
||||||
|
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
|
||||||
|
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
|
||||||
|
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
|
||||||
|
"@aws-cdk/aws-iam:minimizePolicies": true,
|
||||||
|
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
|
||||||
|
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
|
||||||
|
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
|
||||||
|
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
|
||||||
|
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
|
||||||
|
"@aws-cdk/core:enablePartitionLiterals": true,
|
||||||
|
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
|
||||||
|
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
|
||||||
|
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
|
||||||
|
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
|
||||||
|
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
|
||||||
|
"@aws-cdk/aws-route53-patters:useCertificate": true,
|
||||||
|
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
|
||||||
|
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
|
||||||
|
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
|
||||||
|
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
|
||||||
|
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
|
||||||
|
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
|
||||||
|
"@aws-cdk/aws-redshift:columnId": true,
|
||||||
|
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
|
||||||
|
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
|
||||||
|
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
|
||||||
|
"@aws-cdk/aws-kms:aliasNameRef": true,
|
||||||
|
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true
|
||||||
|
}
|
||||||
|
}
|
||||||
4
deploy/scripts/cdk/delete-ecr.sh
Normal file
4
deploy/scripts/cdk/delete-ecr.sh
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
aws cloudformation delete-stack --stack-name LangflowAppStack
|
||||||
|
aws ecr delete-repository --repository-name langflow-backend-repository --force
|
||||||
|
aws ecr delete-repository --repository-name langflow-frontend-repository --force
|
||||||
|
aws ecr describe-repositories --output json | jq -re ".repositories[].repositoryName"
|
||||||
8
deploy/scripts/cdk/jest.config.js
Normal file
8
deploy/scripts/cdk/jest.config.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
module.exports = {
|
||||||
|
testEnvironment: 'node',
|
||||||
|
roots: ['<rootDir>/test'],
|
||||||
|
testMatch: ['**/*.test.ts'],
|
||||||
|
transform: {
|
||||||
|
'^.+\\.tsx?$': 'ts-jest'
|
||||||
|
}
|
||||||
|
};
|
||||||
55
deploy/scripts/cdk/lib/cdk-stack.ts
Normal file
55
deploy/scripts/cdk/lib/cdk-stack.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import * as cdk from 'aws-cdk-lib';
|
||||||
|
import { Construct } from 'constructs';
|
||||||
|
import * as ecs from 'aws-cdk-lib/aws-ecs'
|
||||||
|
|
||||||
|
import { Network, EcrRepository, FrontEndCluster, BackEndCluster, Rds } from './construct';
|
||||||
|
// import * as sqs from 'aws-cdk-lib/aws-sqs';
|
||||||
|
|
||||||
|
export class LangflowAppStack extends cdk.Stack {
|
||||||
|
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
||||||
|
super(scope, id, props);
|
||||||
|
// Arch
|
||||||
|
const arch = ecs.CpuArchitecture.X86_64
|
||||||
|
|
||||||
|
// VPC
|
||||||
|
const { vpc, cluster, alb, targetGroup, cloudmapNamespace, ecsFrontSG, ecsBackSG, dbSG, albSG, backendTaskRole, TaskExecutionRole, frontendTaskRole, backendLogGroup, frontendLogGroup} = new Network(this, 'Network')
|
||||||
|
// ECR
|
||||||
|
const { ecrFrontEndRepository,ecrBackEndRepository} = new EcrRepository(this, 'Ecr', {
|
||||||
|
cloudmapNamespace:cloudmapNamespace,
|
||||||
|
arch:arch
|
||||||
|
})
|
||||||
|
|
||||||
|
// RDS
|
||||||
|
// VPCとSGのリソース情報をPropsとして引き渡す
|
||||||
|
const { rdsCluster } = new Rds(this, 'Rds', { vpc, dbSG })
|
||||||
|
|
||||||
|
const backendService = new BackEndCluster(this, 'backend', {
|
||||||
|
cluster:cluster,
|
||||||
|
ecsBackSG:ecsBackSG,
|
||||||
|
ecrBackEndRepository:ecrBackEndRepository,
|
||||||
|
backendTaskRole:backendTaskRole,
|
||||||
|
backendTaskExecutionRole:TaskExecutionRole,
|
||||||
|
backendLogGroup:backendLogGroup,
|
||||||
|
cloudmapNamespace:cloudmapNamespace,
|
||||||
|
rdsCluster:rdsCluster,
|
||||||
|
alb:alb,
|
||||||
|
arch:arch
|
||||||
|
})
|
||||||
|
|
||||||
|
new FrontEndCluster(this, 'frontend',{
|
||||||
|
cluster:cluster,
|
||||||
|
ecsFrontSG:ecsFrontSG,
|
||||||
|
ecrFrontEndRepository:ecrFrontEndRepository,
|
||||||
|
targetGroup: targetGroup,
|
||||||
|
backendServiceName: backendService.backendServiceName,
|
||||||
|
frontendTaskRole: frontendTaskRole,
|
||||||
|
frontendTaskExecutionRole: TaskExecutionRole,
|
||||||
|
frontendLogGroup: frontendLogGroup,
|
||||||
|
cloudmapNamespace: cloudmapNamespace,
|
||||||
|
arch:arch
|
||||||
|
})
|
||||||
|
|
||||||
|
// S3+CloudFront
|
||||||
|
// new Web(this,'Cloudfront-S3')
|
||||||
|
}
|
||||||
|
}
|
||||||
110
deploy/scripts/cdk/lib/construct/backend.ts
Normal file
110
deploy/scripts/cdk/lib/construct/backend.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { Duration } from 'aws-cdk-lib'
|
||||||
|
import { Construct } from 'constructs';
|
||||||
|
import {
|
||||||
|
aws_ec2 as ec2,
|
||||||
|
aws_ecs as ecs,
|
||||||
|
aws_ecr as ecr,
|
||||||
|
aws_rds as rds,
|
||||||
|
aws_servicediscovery as servicediscovery,
|
||||||
|
aws_iam as iam,
|
||||||
|
aws_logs as logs,
|
||||||
|
aws_elasticloadbalancingv2 as elb,
|
||||||
|
} from 'aws-cdk-lib';
|
||||||
|
|
||||||
|
interface BackEndProps {
|
||||||
|
cluster: ecs.Cluster
|
||||||
|
ecsBackSG:ec2.SecurityGroup
|
||||||
|
ecrBackEndRepository:ecr.Repository
|
||||||
|
backendTaskRole: iam.Role;
|
||||||
|
backendTaskExecutionRole: iam.Role;
|
||||||
|
backendLogGroup: logs.LogGroup;
|
||||||
|
cloudmapNamespace: servicediscovery.PrivateDnsNamespace;
|
||||||
|
rdsCluster:rds.DatabaseCluster
|
||||||
|
alb:elb.IApplicationLoadBalancer
|
||||||
|
arch:ecs.CpuArchitecture
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BackEndCluster extends Construct {
|
||||||
|
readonly backendServiceName: string
|
||||||
|
|
||||||
|
constructor(scope: Construct, id: string, props:BackEndProps) {
|
||||||
|
super(scope, id)
|
||||||
|
const containerPort = 7860
|
||||||
|
// Secrets ManagerからDB認証情報を取ってくる
|
||||||
|
const secretsDB = props.rdsCluster.secret!;
|
||||||
|
|
||||||
|
// Create Backend Fargate Service
|
||||||
|
const backendTaskDefinition = new ecs.FargateTaskDefinition(
|
||||||
|
this,
|
||||||
|
'BackEndTaskDef',
|
||||||
|
{
|
||||||
|
memoryLimitMiB: 512,
|
||||||
|
cpu: 256,
|
||||||
|
executionRole: props.backendTaskExecutionRole,
|
||||||
|
runtimePlatform:{
|
||||||
|
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
|
||||||
|
cpuArchitecture: props.arch,
|
||||||
|
},
|
||||||
|
taskRole: props.backendTaskRole,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
backendTaskDefinition.addContainer('backendContainer', {
|
||||||
|
image: ecs.ContainerImage.fromEcrRepository(props.ecrBackEndRepository, "latest"),
|
||||||
|
containerName:'langflow-back-container',
|
||||||
|
logging: ecs.LogDriver.awsLogs({
|
||||||
|
streamPrefix: 'my-stream',
|
||||||
|
logGroup: props.backendLogGroup,
|
||||||
|
}),
|
||||||
|
environment:{
|
||||||
|
// user:pass@endpoint:port/dbname
|
||||||
|
"LANGFLOW_DATABASE_URL" : `mysql+pymysql://${ecs.Secret.fromSecretsManager(secretsDB, 'username')}:${ecs.Secret.fromSecretsManager(secretsDB, 'password')}@${ecs.Secret.fromSecretsManager(secretsDB, 'host')}:3306/${ecs.Secret.fromSecretsManager(secretsDB, 'dbname')}`,
|
||||||
|
"LANGFLOW_AUTO_LOGIN" : "false",
|
||||||
|
"LANGFLOW_SUPERUSER" : "admin",
|
||||||
|
"LANGFLOW_SUPERUSER_PASSWORD" : "1234567"
|
||||||
|
},
|
||||||
|
portMappings: [
|
||||||
|
{
|
||||||
|
containerPort: containerPort,
|
||||||
|
protocol: ecs.Protocol.TCP,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// Secretの設定
|
||||||
|
secrets: {
|
||||||
|
"dbname": ecs.Secret.fromSecretsManager(secretsDB, 'dbname'),
|
||||||
|
"username": ecs.Secret.fromSecretsManager(secretsDB, 'username'),
|
||||||
|
"host": ecs.Secret.fromSecretsManager(secretsDB, 'host'),
|
||||||
|
"password": ecs.Secret.fromSecretsManager(secretsDB, 'password'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.backendServiceName = 'langflow-backend-service'
|
||||||
|
const backendService = new ecs.FargateService(this, 'BackEndService', {
|
||||||
|
cluster: props.cluster,
|
||||||
|
serviceName: this.backendServiceName,
|
||||||
|
taskDefinition: backendTaskDefinition,
|
||||||
|
enableExecuteCommand: true,
|
||||||
|
securityGroups: [props.ecsBackSG],
|
||||||
|
cloudMapOptions: {
|
||||||
|
cloudMapNamespace: props.cloudmapNamespace,
|
||||||
|
containerPort: containerPort,
|
||||||
|
dnsRecordType: servicediscovery.DnsRecordType.A,
|
||||||
|
dnsTtl: Duration.seconds(10),
|
||||||
|
name: this.backendServiceName
|
||||||
|
},
|
||||||
|
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
|
||||||
|
});
|
||||||
|
|
||||||
|
const ecsBackEndExecutionRole = iam.Role.fromRoleArn(
|
||||||
|
this,
|
||||||
|
"ecsBackEndExecutionRole",
|
||||||
|
backendService.taskDefinition.executionRole!.roleArn,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
ecsBackEndExecutionRole.attachInlinePolicy(new iam.Policy(this, 'SMGetPolicy', {
|
||||||
|
statements: [new iam.PolicyStatement({
|
||||||
|
actions: ['secretsmanager:GetSecretValue'],
|
||||||
|
resources: [secretsDB.secretArn],
|
||||||
|
})],
|
||||||
|
}));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
64
deploy/scripts/cdk/lib/construct/db.ts
Normal file
64
deploy/scripts/cdk/lib/construct/db.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { Construct } from 'constructs';
|
||||||
|
import * as ec2 from 'aws-cdk-lib/aws-ec2'
|
||||||
|
import * as rds from "aws-cdk-lib/aws-rds";
|
||||||
|
|
||||||
|
interface RdsProps {
|
||||||
|
vpc: ec2.Vpc
|
||||||
|
dbSG:ec2.SecurityGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Rds extends Construct{
|
||||||
|
readonly rdsCluster: rds.DatabaseCluster
|
||||||
|
|
||||||
|
constructor(scope: Construct, id:string, props: RdsProps){
|
||||||
|
super(scope, id);
|
||||||
|
|
||||||
|
const {vpc, dbSG} = props
|
||||||
|
const instanceType = ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE4_GRAVITON, ec2.InstanceSize.MEDIUM)
|
||||||
|
|
||||||
|
// RDSのパスワードを自動生成してSecrets Managerに格納
|
||||||
|
const rdsCredentials = rds.Credentials.fromGeneratedSecret('db_user',{
|
||||||
|
secretName: 'langflow-DbSecret',
|
||||||
|
})
|
||||||
|
|
||||||
|
// DB クラスターのパラメータグループ作成
|
||||||
|
const clusterParameterGroup = new rds.ParameterGroup(scope, 'ClusterParameterGroup',{
|
||||||
|
engine: rds.DatabaseClusterEngine.auroraMysql({
|
||||||
|
version: rds.AuroraMysqlEngineVersion.VER_3_02_0
|
||||||
|
}),
|
||||||
|
description: 'for-langflow',
|
||||||
|
})
|
||||||
|
clusterParameterGroup.bindToCluster({})
|
||||||
|
|
||||||
|
// DB インスタンスのパラメタグループ作成
|
||||||
|
const instanceParameterGroup = new rds.ParameterGroup(scope, 'InstanceParameterGroup',{
|
||||||
|
engine: rds.DatabaseClusterEngine.auroraMysql({
|
||||||
|
version: rds.AuroraMysqlEngineVersion.VER_3_02_0,
|
||||||
|
}),
|
||||||
|
description: 'for-langflow',
|
||||||
|
})
|
||||||
|
instanceParameterGroup.bindToInstance({})
|
||||||
|
|
||||||
|
this.rdsCluster = new rds.DatabaseCluster(scope, 'LangflowDbCluster', {
|
||||||
|
engine: rds.DatabaseClusterEngine.auroraMysql({
|
||||||
|
version: rds.AuroraMysqlEngineVersion.VER_3_02_0,
|
||||||
|
}),
|
||||||
|
storageEncrypted: true,
|
||||||
|
credentials: rdsCredentials,
|
||||||
|
instanceIdentifierBase: 'langflow-instance',
|
||||||
|
vpc:vpc,
|
||||||
|
vpcSubnets:vpc.selectSubnets({
|
||||||
|
subnetGroupName: 'langflow-Isolated',
|
||||||
|
}),
|
||||||
|
securityGroups:[dbSG],
|
||||||
|
writer: rds.ClusterInstance.provisioned("WriterInstance", {
|
||||||
|
instanceType: instanceType,
|
||||||
|
enablePerformanceInsights: true,
|
||||||
|
parameterGroup:instanceParameterGroup,
|
||||||
|
}),
|
||||||
|
// 2台目以降はreaders:で設定
|
||||||
|
parameterGroup: clusterParameterGroup,
|
||||||
|
defaultDatabaseName: 'langflow',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
76
deploy/scripts/cdk/lib/construct/ecr.ts
Normal file
76
deploy/scripts/cdk/lib/construct/ecr.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { RemovalPolicy } from 'aws-cdk-lib'
|
||||||
|
import * as ecr from 'aws-cdk-lib/aws-ecr'
|
||||||
|
import * as ecrdeploy from 'cdk-ecr-deployment'
|
||||||
|
import * as ecs from 'aws-cdk-lib/aws-ecs'
|
||||||
|
import * as servicediscovery from 'aws-cdk-lib/aws-servicediscovery'
|
||||||
|
import { DockerImageAsset, Platform } from 'aws-cdk-lib/aws-ecr-assets'
|
||||||
|
import * as path from "path";
|
||||||
|
import { Construct } from 'constructs'
|
||||||
|
|
||||||
|
|
||||||
|
interface ECRProps {
|
||||||
|
cloudmapNamespace: servicediscovery.PrivateDnsNamespace;
|
||||||
|
arch:ecs.CpuArchitecture;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EcrRepository extends Construct {
|
||||||
|
readonly ecrFrontEndRepository: ecr.Repository
|
||||||
|
readonly ecrBackEndRepository: ecr.Repository
|
||||||
|
|
||||||
|
constructor(scope: Construct, id: string, props: ECRProps) {
|
||||||
|
super(scope, id)
|
||||||
|
|
||||||
|
const imagePlatform = props.arch == ecs.CpuArchitecture.ARM64 ? Platform.LINUX_ARM64 : Platform.LINUX_AMD64
|
||||||
|
const backendPath = path.join(__dirname, "../../../../../../", "langflow")
|
||||||
|
const frontendPath = path.join(__dirname, "../../../../../src/", "frontend")
|
||||||
|
const excludeDir = ['node_modules','.git', 'cdk.out']
|
||||||
|
const LifecycleRule = {
|
||||||
|
tagStatus: ecr.TagStatus.ANY,
|
||||||
|
description: 'Delete more than 30 image',
|
||||||
|
maxImageCount: 30,
|
||||||
|
}
|
||||||
|
|
||||||
|
// リポジトリ作成
|
||||||
|
this.ecrFrontEndRepository = new ecr.Repository(scope, 'LangflowFrontEndRepository', {
|
||||||
|
repositoryName: 'langflow-frontend-repository',
|
||||||
|
removalPolicy: RemovalPolicy.RETAIN,
|
||||||
|
imageScanOnPush: true,
|
||||||
|
})
|
||||||
|
this.ecrBackEndRepository = new ecr.Repository(scope, 'LangflowBackEndRepository', {
|
||||||
|
repositoryName: 'langflow-backend-repository',
|
||||||
|
removalPolicy: RemovalPolicy.RETAIN,
|
||||||
|
imageScanOnPush: true,
|
||||||
|
})
|
||||||
|
// LifecycleRule作成
|
||||||
|
this.ecrFrontEndRepository.addLifecycleRule(LifecycleRule)
|
||||||
|
this.ecrBackEndRepository.addLifecycleRule(LifecycleRule)
|
||||||
|
|
||||||
|
// Create Docker Image Asset
|
||||||
|
const dockerFrontEndImageAsset = new DockerImageAsset(this, "DockerFrontEndImageAsset", {
|
||||||
|
directory: frontendPath,
|
||||||
|
buildArgs:{
|
||||||
|
"BACKEND_URL":`http://${props.cloudmapNamespace.namespaceName}:7860`
|
||||||
|
},
|
||||||
|
exclude: excludeDir,
|
||||||
|
platform: imagePlatform,
|
||||||
|
});
|
||||||
|
const dockerBackEndImageAsset = new DockerImageAsset(this, "DockerBackEndImageAsset", {
|
||||||
|
directory: backendPath,
|
||||||
|
exclude: excludeDir,
|
||||||
|
platform: imagePlatform,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Deploy Docker Image to ECR Repository
|
||||||
|
new ecrdeploy.ECRDeployment(this, "DeployFrontEndImage", {
|
||||||
|
src: new ecrdeploy.DockerImageName(dockerFrontEndImageAsset.imageUri),
|
||||||
|
dest: new ecrdeploy.DockerImageName(this.ecrFrontEndRepository.repositoryUri)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Deploy Docker Image to ECR Repository
|
||||||
|
new ecrdeploy.ECRDeployment(this, "DeployBackEndImage", {
|
||||||
|
src: new ecrdeploy.DockerImageName(dockerBackEndImageAsset.imageUri),
|
||||||
|
dest: new ecrdeploy.DockerImageName(this.ecrBackEndRepository.repositoryUri)
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
109
deploy/scripts/cdk/lib/construct/frontend.ts
Normal file
109
deploy/scripts/cdk/lib/construct/frontend.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
import { Duration } from 'aws-cdk-lib'
|
||||||
|
import { Construct } from 'constructs'
|
||||||
|
import {
|
||||||
|
aws_ec2 as ec2,
|
||||||
|
aws_ecs as ecs,
|
||||||
|
aws_ecr as ecr,
|
||||||
|
aws_servicediscovery as servicediscovery,
|
||||||
|
aws_iam as iam,
|
||||||
|
aws_logs as logs,
|
||||||
|
aws_elasticloadbalancingv2 as elb,
|
||||||
|
} from 'aws-cdk-lib';
|
||||||
|
import { CpuArchitecture } from 'aws-cdk-lib/aws-ecs';
|
||||||
|
|
||||||
|
interface FrontEndProps {
|
||||||
|
cluster:ecs.Cluster
|
||||||
|
ecsFrontSG:ec2.SecurityGroup
|
||||||
|
ecrFrontEndRepository:ecr.Repository
|
||||||
|
targetGroup: elb.ApplicationTargetGroup;
|
||||||
|
backendServiceName: string;
|
||||||
|
frontendTaskRole: iam.Role;
|
||||||
|
frontendTaskExecutionRole: iam.Role;
|
||||||
|
frontendLogGroup: logs.LogGroup;
|
||||||
|
cloudmapNamespace: servicediscovery.PrivateDnsNamespace;
|
||||||
|
arch:ecs.CpuArchitecture;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FrontEndCluster extends Construct {
|
||||||
|
constructor(scope: Construct, id: string, props:FrontEndProps) {
|
||||||
|
super(scope, id)
|
||||||
|
|
||||||
|
const containerPort = 3000
|
||||||
|
const frontendTaskDefinition = new ecs.FargateTaskDefinition(
|
||||||
|
this,
|
||||||
|
'FrontendTaskDef',
|
||||||
|
{
|
||||||
|
memoryLimitMiB: 512,
|
||||||
|
cpu: 256,
|
||||||
|
executionRole: props.frontendTaskExecutionRole,
|
||||||
|
runtimePlatform:{
|
||||||
|
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
|
||||||
|
cpuArchitecture: props.arch,
|
||||||
|
},
|
||||||
|
taskRole: props.frontendTaskRole,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
frontendTaskDefinition.addContainer('frontendContainer', {
|
||||||
|
image: ecs.ContainerImage.fromEcrRepository(props.ecrFrontEndRepository, "latest"),
|
||||||
|
containerName:'langflow-front-container',
|
||||||
|
environment: {
|
||||||
|
BACKEND_SERVICE_NAME: props.backendServiceName,
|
||||||
|
BACKEND_URL: `http://${props.backendServiceName}.${props.cloudmapNamespace.namespaceName}:7860/`,
|
||||||
|
VITE_PROXY_TARGET: `http://${props.backendServiceName}.${props.cloudmapNamespace.namespaceName}:7860/`,
|
||||||
|
},
|
||||||
|
logging: ecs.LogDriver.awsLogs({
|
||||||
|
streamPrefix: 'my-stream',
|
||||||
|
logGroup: props.frontendLogGroup,
|
||||||
|
}),
|
||||||
|
portMappings: [
|
||||||
|
{
|
||||||
|
containerPort: containerPort,
|
||||||
|
protocol: ecs.Protocol.TCP,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const frontendService = new ecs.FargateService(
|
||||||
|
this,
|
||||||
|
'FrontendService',
|
||||||
|
{
|
||||||
|
serviceName: 'langflow-frontend-service',
|
||||||
|
cluster: props.cluster,
|
||||||
|
desiredCount: 1,
|
||||||
|
assignPublicIp: false,
|
||||||
|
taskDefinition: frontendTaskDefinition,
|
||||||
|
enableExecuteCommand: true,
|
||||||
|
securityGroups: [props.ecsFrontSG],
|
||||||
|
// healthCheckGracePeriod: Duration.seconds(300),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// props.targetGroup.addTarget(frontendService);
|
||||||
|
|
||||||
|
// // Create ALB and ECS Fargate Service
|
||||||
|
// const frontService = new ecs_patterns.ApplicationLoadBalancedFargateService(
|
||||||
|
// this,
|
||||||
|
// "FrontEndService",
|
||||||
|
// {
|
||||||
|
// cluster: cluster,
|
||||||
|
// serviceName: 'langflow-frontend-service',
|
||||||
|
// cpu: 256,
|
||||||
|
// memoryLimitMiB: 512,
|
||||||
|
// listenerPort: 80,
|
||||||
|
// assignPublicIp: true, // Public facing - ALB
|
||||||
|
// taskSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
|
||||||
|
// securityGroups:[ecsFrontSG],
|
||||||
|
// taskImageOptions: {
|
||||||
|
// family: 'langflow-taskdef',
|
||||||
|
// containerName: 'langflow-front-container',
|
||||||
|
// image: ecs.ContainerImage.fromEcrRepository(ecrFrontEndRepository, "latest"),
|
||||||
|
// containerPort: 3000, // L2なので、TargetGroupのportが3000で設定されるはず
|
||||||
|
// },
|
||||||
|
// loadBalancer:alb,
|
||||||
|
// openListener:false,
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
5
deploy/scripts/cdk/lib/construct/index.ts
Normal file
5
deploy/scripts/cdk/lib/construct/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from './db';
|
||||||
|
export * from './ecr';
|
||||||
|
export * from './frontend';
|
||||||
|
export * from './backend';
|
||||||
|
export * from './network';
|
||||||
180
deploy/scripts/cdk/lib/construct/network.ts
Normal file
180
deploy/scripts/cdk/lib/construct/network.ts
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
import { RemovalPolicy, Duration } from 'aws-cdk-lib'
|
||||||
|
import { Construct } from 'constructs'
|
||||||
|
import {
|
||||||
|
aws_ec2 as ec2,
|
||||||
|
aws_ecs as ecs,
|
||||||
|
aws_dynamodb as dynamodb,
|
||||||
|
aws_iam as iam,
|
||||||
|
aws_logs as logs,
|
||||||
|
aws_servicediscovery as servicediscovery,
|
||||||
|
aws_elasticloadbalancingv2 as elb,
|
||||||
|
} from 'aws-cdk-lib';
|
||||||
|
|
||||||
|
export class Network extends Construct {
|
||||||
|
readonly vpc: ec2.Vpc;
|
||||||
|
readonly cluster: ecs.Cluster;
|
||||||
|
readonly alb: elb.IApplicationLoadBalancer;
|
||||||
|
readonly targetGroup: elb.ApplicationTargetGroup;
|
||||||
|
readonly cloudmapNamespace: servicediscovery.PrivateDnsNamespace;
|
||||||
|
readonly ecsFrontSG: ec2.SecurityGroup;
|
||||||
|
readonly ecsBackSG: ec2.SecurityGroup;
|
||||||
|
readonly dbSG: ec2.SecurityGroup;
|
||||||
|
readonly albSG: ec2.SecurityGroup;
|
||||||
|
readonly backendTaskRole: iam.Role;
|
||||||
|
readonly TaskExecutionRole: iam.Role;
|
||||||
|
readonly frontendTaskRole: iam.Role;
|
||||||
|
readonly backendLogGroup: logs.LogGroup;
|
||||||
|
readonly frontendLogGroup: logs.LogGroup;
|
||||||
|
|
||||||
|
constructor(scope: Construct, id: string) {
|
||||||
|
super(scope, id)
|
||||||
|
|
||||||
|
// VPC等リソースの作成
|
||||||
|
this.vpc = new ec2.Vpc(scope, 'VPC', {
|
||||||
|
vpcName: 'langflow-vpc',
|
||||||
|
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
|
||||||
|
maxAzs: 3,
|
||||||
|
subnetConfiguration: [
|
||||||
|
{
|
||||||
|
cidrMask: 24,
|
||||||
|
name: 'langflow-Isolated',
|
||||||
|
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cidrMask: 24,
|
||||||
|
name: 'langflow-Public',
|
||||||
|
subnetType: ec2.SubnetType.PUBLIC,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cidrMask: 24,
|
||||||
|
name: 'langflow-Private',
|
||||||
|
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS
|
||||||
|
},
|
||||||
|
],
|
||||||
|
natGateways: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.cluster = new ecs.Cluster(this, 'EcsCluster', {
|
||||||
|
clusterName: 'langflow-cluster',
|
||||||
|
vpc: this.vpc,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Private DNS
|
||||||
|
this.cloudmapNamespace = new servicediscovery.PrivateDnsNamespace(
|
||||||
|
this,
|
||||||
|
'Namespace',
|
||||||
|
{
|
||||||
|
name: 'ecs-deploy.com',
|
||||||
|
vpc: this.vpc,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ALBに設定するセキュリティグループ
|
||||||
|
this.albSG = new ec2.SecurityGroup(scope, 'ALBSecurityGroup', {
|
||||||
|
securityGroupName: 'alb-sg',
|
||||||
|
description: 'for alb',
|
||||||
|
vpc: this.vpc,
|
||||||
|
})
|
||||||
|
this.albSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80))
|
||||||
|
|
||||||
|
this.alb = new elb.ApplicationLoadBalancer(this,'alb',{
|
||||||
|
internetFacing: true, //インターネットからのアクセスを許可するかどうか指定
|
||||||
|
loadBalancerName: 'alb',
|
||||||
|
securityGroup: this.albSG, //作成したセキュリティグループを割り当てる
|
||||||
|
vpc:this.vpc,
|
||||||
|
})
|
||||||
|
|
||||||
|
const listener = this.alb.addListener('Listener', { port: 80 });
|
||||||
|
|
||||||
|
this.targetGroup = listener.addTargets('targetGroup', {
|
||||||
|
port: 3000,
|
||||||
|
protocol: elb.ApplicationProtocol.HTTP,
|
||||||
|
healthCheck: {
|
||||||
|
enabled: true,
|
||||||
|
path: '/health',
|
||||||
|
healthyThresholdCount: 2,
|
||||||
|
unhealthyThresholdCount: 4,
|
||||||
|
interval: Duration.seconds(100),
|
||||||
|
timeout: Duration.seconds(30),
|
||||||
|
healthyHttpCodes: '200',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ECS FrontEndに設定するセキュリティグループ
|
||||||
|
this.ecsFrontSG = new ec2.SecurityGroup(scope, 'ECSFrontEndSecurityGroup', {
|
||||||
|
securityGroupName: 'langflow-ecs-front-sg',
|
||||||
|
description: 'for langflow-front-ecs',
|
||||||
|
vpc: this.vpc,
|
||||||
|
})
|
||||||
|
this.ecsFrontSG.addIngressRule(this.albSG, ec2.Port.allTcp())
|
||||||
|
|
||||||
|
// ECS BackEndに設定するセキュリティグループ
|
||||||
|
this.ecsBackSG = new ec2.SecurityGroup(scope, 'ECSBackEndSecurityGroup', {
|
||||||
|
securityGroupName: 'langflow-ecs-back-sg',
|
||||||
|
description: 'for langflow-back-ecs',
|
||||||
|
vpc: this.vpc,
|
||||||
|
})
|
||||||
|
this.ecsBackSG.addIngressRule(this.ecsFrontSG, ec2.Port.tcp(7860))
|
||||||
|
|
||||||
|
// RDSに設定するセキュリティグループ
|
||||||
|
this.dbSG = new ec2.SecurityGroup(scope, 'DBSecurityGroup', {
|
||||||
|
allowAllOutbound: true,
|
||||||
|
securityGroupName: 'langflow-db',
|
||||||
|
description: 'for langflow-db',
|
||||||
|
vpc: this.vpc,
|
||||||
|
})
|
||||||
|
// AppRunnerSecurityGroupからのポート3306:mysql(5432:postgres)のインバウンドを許可
|
||||||
|
this.dbSG.addIngressRule(this.ecsBackSG, ec2.Port.tcp(3306))
|
||||||
|
|
||||||
|
// ECS Policy State
|
||||||
|
const ECSExecPolicyStatement = new iam.PolicyStatement({
|
||||||
|
sid: 'allowECSExec',
|
||||||
|
resources: ['*'],
|
||||||
|
actions: [
|
||||||
|
'ecr:GetAuthorizationToken',
|
||||||
|
'ecr:BatchCheckLayerAvailability',
|
||||||
|
'ecr:GetDownloadUrlForLayer',
|
||||||
|
'ecr:BatchGetImage',
|
||||||
|
'ssmmessages:CreateControlChannel',
|
||||||
|
'ssmmessages:CreateDataChannel',
|
||||||
|
'ssmmessages:OpenControlChannel',
|
||||||
|
'ssmmessages:OpenDataChannel',
|
||||||
|
'logs:CreateLogStream',
|
||||||
|
'logs:DescribeLogGroups',
|
||||||
|
'logs:DescribeLogStreams',
|
||||||
|
'logs:PutLogEvents',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.backendTaskRole = new iam.Role(this, 'BackendTaskRole', {
|
||||||
|
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
||||||
|
});
|
||||||
|
this.backendTaskRole.addToPolicy(ECSExecPolicyStatement);
|
||||||
|
|
||||||
|
this.frontendTaskRole = new iam.Role(this, 'FrontendTaskRole', {
|
||||||
|
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
||||||
|
});
|
||||||
|
this.frontendTaskRole.addToPolicy(ECSExecPolicyStatement);
|
||||||
|
|
||||||
|
this.TaskExecutionRole = new iam.Role(this, 'TaskExecutionRole', {
|
||||||
|
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
||||||
|
managedPolicies: [
|
||||||
|
{
|
||||||
|
managedPolicyArn:
|
||||||
|
'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create CloudWatch Log Group
|
||||||
|
this.backendLogGroup = new logs.LogGroup(this, 'backendLogGroup', {
|
||||||
|
logGroupName: 'myapp-backend',
|
||||||
|
removalPolicy: RemovalPolicy.DESTROY,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.frontendLogGroup = new logs.LogGroup(this, 'frontendLogGroup', {
|
||||||
|
logGroupName: 'myapp-frontend',
|
||||||
|
removalPolicy: RemovalPolicy.DESTROY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
4601
deploy/scripts/cdk/package-lock.json
generated
Normal file
4601
deploy/scripts/cdk/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
28
deploy/scripts/cdk/package.json
Normal file
28
deploy/scripts/cdk/package.json
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "cdk",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"bin": {
|
||||||
|
"cdk": "bin/cdk.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"watch": "tsc -w",
|
||||||
|
"test": "jest",
|
||||||
|
"cdk": "cdk"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jest": "^29.5.1",
|
||||||
|
"@types/node": "20.1.7",
|
||||||
|
"aws-cdk": "2.86.0",
|
||||||
|
"jest": "^29.5.0",
|
||||||
|
"ts-jest": "^29.1.0",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"typescript": "~5.1.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"aws-cdk-lib": "2.86.0",
|
||||||
|
"cdk-ecr-deployment": "^2.5.30",
|
||||||
|
"constructs": "^10.0.0",
|
||||||
|
"source-map-support": "^0.5.21"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
deploy/scripts/cdk/test/cdk.test.ts
Normal file
17
deploy/scripts/cdk/test/cdk.test.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
// import * as cdk from 'aws-cdk-lib';
|
||||||
|
// import { Template } from 'aws-cdk-lib/assertions';
|
||||||
|
// import * as Cdk from '../lib/cdk-stack';
|
||||||
|
|
||||||
|
// example test. To run these tests, uncomment this file along with the
|
||||||
|
// example resource in lib/cdk-stack.ts
|
||||||
|
test('SQS Queue Created', () => {
|
||||||
|
// const app = new cdk.App();
|
||||||
|
// // WHEN
|
||||||
|
// const stack = new Cdk.CdkStack(app, 'MyTestStack');
|
||||||
|
// // THEN
|
||||||
|
// const template = Template.fromStack(stack);
|
||||||
|
|
||||||
|
// template.hasResourceProperties('AWS::SQS::Queue', {
|
||||||
|
// VisibilityTimeout: 300
|
||||||
|
// });
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue