Fix bug in deployment procedure in AWS CDK and support HTTPS (#1491)
* add cloudfront * modify: healthcheck disable * modify: health check option * modify nodejsbuild * add nodejs version * endpoint configuration * set axios url * remote console.log VITE_PROXY_TARGET * create alb stack * add alb to origin * alb build success * remote baseURL setting * add redirect * change responseHttpStatus to 200 * modify output and readme * modify architecture * add health check path route to cloudfront routing * modified: c9 env name in deploy langflow section * modified : package dependency for pymysql * modified: deploy procedure * modified: deploy procedure (ja) --------- Co-authored-by: nsxshota <nsxshota@amazon.co.jp> Co-authored-by: ymkazuki <ymkazuki@amazon.co.jp> Co-authored-by: Shota Nakamoto <53632932+nsy0328@users.noreply.github.com>
This commit is contained in:
parent
7363df6293
commit
3eeda84d28
18 changed files with 678 additions and 271 deletions
|
|
@ -2,21 +2,38 @@ 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, EcsIAM } from './construct';
|
||||
import { Network, EcrRepository, Web, BackEndCluster, Rds, EcsIAM, Rag} from './construct';
|
||||
// import * as sqs from 'aws-cdk-lib/aws-sqs';
|
||||
|
||||
const errorMessageForBooleanContext = (key: string) => {
|
||||
return `There was an error setting $ {key}. Possible causes are as follows.
|
||||
- Trying to set it with the -c option instead of changing cdk.json
|
||||
- cdk.json is set to a value that is not a boolean (e.g. “true” double quotes are not required)
|
||||
- no items in cdk.json (unset) `;
|
||||
};
|
||||
|
||||
|
||||
export class LangflowAppStack extends cdk.Stack {
|
||||
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
||||
super(scope, id, props);
|
||||
// Kendra Enable
|
||||
const ragEnabled: boolean = this.node.tryGetContext('ragEnabled')!;
|
||||
if (typeof ragEnabled !== 'boolean') {
|
||||
throw new Error(errorMessageForBooleanContext('ragEnabled'));
|
||||
}
|
||||
if (ragEnabled) {
|
||||
new Rag(this, 'Rag', {
|
||||
});
|
||||
}
|
||||
|
||||
// Arch
|
||||
const arch = ecs.CpuArchitecture.X86_64
|
||||
|
||||
// VPC
|
||||
const { vpc, cluster, alb, targetGroup, cloudmapNamespace, ecsFrontSG, ecsBackSG, dbSG, albSG, backendLogGroup, frontendLogGroup} = new Network(this, 'Network')
|
||||
const { vpc, cluster, ecsBackSG, dbSG, backendLogGroup, alb, albTG, albSG} = new Network(this, 'Network')
|
||||
|
||||
// ECR
|
||||
const { ecrFrontEndRepository,ecrBackEndRepository} = new EcrRepository(this, 'Ecr', {
|
||||
cloudmapNamespace:cloudmapNamespace,
|
||||
const { ecrBackEndRepository } = new EcrRepository(this, 'Ecr', {
|
||||
arch:arch
|
||||
})
|
||||
|
||||
|
|
@ -25,7 +42,7 @@ export class LangflowAppStack extends cdk.Stack {
|
|||
const { rdsCluster } = new Rds(this, 'Rds', { vpc, dbSG })
|
||||
|
||||
// IAM
|
||||
const { frontendTaskRole, frontendTaskExecutionRole, backendTaskRole, backendTaskExecutionRole } = new EcsIAM(this, 'EcsIAM',{
|
||||
const { backendTaskRole, backendTaskExecutionRole } = new EcsIAM(this, 'EcsIAM',{
|
||||
rdsCluster:rdsCluster
|
||||
})
|
||||
|
||||
|
|
@ -36,29 +53,18 @@ export class LangflowAppStack extends cdk.Stack {
|
|||
backendTaskRole:backendTaskRole,
|
||||
backendTaskExecutionRole:backendTaskExecutionRole,
|
||||
backendLogGroup:backendLogGroup,
|
||||
cloudmapNamespace:cloudmapNamespace,
|
||||
rdsCluster:rdsCluster,
|
||||
alb:alb,
|
||||
arch:arch
|
||||
arch:arch,
|
||||
albTG:albTG
|
||||
})
|
||||
backendService.node.addDependency(rdsCluster);
|
||||
|
||||
const frontendService = new FrontEndCluster(this, 'frontend',{
|
||||
const frontendService = new Web(this, 'frontend',{
|
||||
cluster:cluster,
|
||||
ecsFrontSG:ecsFrontSG,
|
||||
ecrFrontEndRepository:ecrFrontEndRepository,
|
||||
targetGroup: targetGroup,
|
||||
backendServiceName: backendService.backendServiceName,
|
||||
frontendTaskRole: frontendTaskRole,
|
||||
frontendTaskExecutionRole: frontendTaskExecutionRole,
|
||||
frontendLogGroup: frontendLogGroup,
|
||||
cloudmapNamespace: cloudmapNamespace,
|
||||
arch:arch
|
||||
alb:alb,
|
||||
albSG:albSG
|
||||
})
|
||||
frontendService.node.addDependency(backendService);
|
||||
|
||||
|
||||
// S3+CloudFront
|
||||
// new Web(this,'Cloudfront-S3')
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,18 +21,17 @@ interface BackEndProps {
|
|||
backendTaskRole: iam.Role;
|
||||
backendTaskExecutionRole: iam.Role;
|
||||
backendLogGroup: logs.LogGroup;
|
||||
cloudmapNamespace: servicediscovery.PrivateDnsNamespace;
|
||||
rdsCluster:rds.DatabaseCluster
|
||||
alb:elb.IApplicationLoadBalancer
|
||||
arch:ecs.CpuArchitecture
|
||||
albTG: elb.ApplicationTargetGroup;
|
||||
}
|
||||
|
||||
export class BackEndCluster extends Construct {
|
||||
readonly backendServiceName: string
|
||||
|
||||
constructor(scope: Construct, id: string, props:BackEndProps) {
|
||||
super(scope, id)
|
||||
const containerPort = 7860
|
||||
const backendServiceName = 'backend'
|
||||
const backendServicePort = 7860
|
||||
// Secrets ManagerからDB認証情報を取ってくる
|
||||
const secretsDB = props.rdsCluster.secret!;
|
||||
|
||||
|
|
@ -59,20 +58,13 @@ export class BackEndCluster extends Construct {
|
|||
logGroup: props.backendLogGroup,
|
||||
}),
|
||||
environment:{
|
||||
// user:pass@endpoint:port/dbname
|
||||
// "LANGFLOW_DATABASE_URL" : `mysql+pymysql://${username}:${password}@${host}:3306/${dbname}`,
|
||||
// "LANGFLOW_DATABASE_URL" : "sqlite:///./langflow.db",
|
||||
// "LANGFLOW_LANGCHAIN_CACHE" : "SQLiteCache",
|
||||
// "LANGFLOW_AUTO_LOGIN" : "false",
|
||||
// "LANGFLOW_SUPERUSER" : "admin",
|
||||
// "LANGFLOW_SUPERUSER_PASSWORD" : "1234567"
|
||||
"LANGFLOW_AUTO_LOGIN" : process.env.LANGFLOW_AUTO_LOGIN ?? 'false',
|
||||
"LANGFLOW_SUPERUSER" : process.env.LANGFLOW_SUPERUSER ?? "admin",
|
||||
"LANGFLOW_SUPERUSER_PASSWORD" : process.env.LANGFLOW_SUPERUSER_PASSWORD ?? "123456"
|
||||
},
|
||||
portMappings: [
|
||||
{
|
||||
containerPort: containerPort,
|
||||
containerPort: backendServicePort,
|
||||
protocol: ecs.Protocol.TCP,
|
||||
},
|
||||
],
|
||||
|
|
@ -84,22 +76,15 @@ export class BackEndCluster extends Construct {
|
|||
"password": ecs.Secret.fromSecretsManager(secretsDB, 'password'),
|
||||
},
|
||||
});
|
||||
this.backendServiceName = 'backend'
|
||||
|
||||
const backendService = new ecs.FargateService(this, 'BackEndService', {
|
||||
cluster: props.cluster,
|
||||
serviceName: this.backendServiceName,
|
||||
serviceName: 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 },
|
||||
});
|
||||
|
||||
props.albTG.addTarget(backendService);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,12 +9,10 @@ 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) {
|
||||
|
|
@ -22,7 +20,6 @@ export class EcrRepository extends Construct {
|
|||
|
||||
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,
|
||||
|
|
@ -30,31 +27,16 @@ export class EcrRepository extends Construct {
|
|||
maxImageCount: 30,
|
||||
}
|
||||
|
||||
// リポジトリ作成
|
||||
this.ecrFrontEndRepository = new ecr.Repository(scope, 'LangflowFrontEndRepository', {
|
||||
repositoryName: 'langflow-frontend-repository',
|
||||
removalPolicy: RemovalPolicy.RETAIN,
|
||||
imageScanOnPush: true,
|
||||
})
|
||||
// Backend ECR リポジトリ作成
|
||||
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,
|
||||
file:"cdk.Dockerfile",
|
||||
buildArgs:{
|
||||
"BACKEND_URL":`http://backend.${props.cloudmapNamespace.namespaceName}:7860`
|
||||
},
|
||||
exclude: excludeDir,
|
||||
platform: imagePlatform,
|
||||
});
|
||||
const dockerBackEndImageAsset = new DockerImageAsset(this, "DockerBackEndImageAsset", {
|
||||
directory: backendPath,
|
||||
file:"cdk.Dockerfile",
|
||||
|
|
@ -62,12 +44,6 @@ export class EcrRepository extends Construct {
|
|||
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),
|
||||
|
|
|
|||
|
|
@ -1,117 +1,141 @@
|
|||
import { Duration } from 'aws-cdk-lib'
|
||||
import { Construct } from 'constructs'
|
||||
import { Stack, Duration, RemovalPolicy, CfnOutput } 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_s3 as s3,
|
||||
aws_iam as iam,
|
||||
aws_logs as logs,
|
||||
aws_elasticloadbalancingv2 as elb,
|
||||
aws_cloudfront as cloudfront,
|
||||
aws_cloudfront_origins as origins,
|
||||
aws_s3_deployment as s3_deployment
|
||||
} from 'aws-cdk-lib';
|
||||
import { CpuArchitecture } from 'aws-cdk-lib/aws-ecs';
|
||||
import { CloudFrontToS3 } from '@aws-solutions-constructs/aws-cloudfront-s3';
|
||||
import { CfnDistribution, Distribution } from 'aws-cdk-lib/aws-cloudfront';
|
||||
import { NodejsBuild } from 'deploy-time-build';
|
||||
|
||||
interface FrontEndProps {
|
||||
interface WebProps {
|
||||
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;
|
||||
alb:elb.IApplicationLoadBalancer;
|
||||
albSG:ec2.SecurityGroup;
|
||||
}
|
||||
|
||||
export class FrontEndCluster extends Construct {
|
||||
constructor(scope: Construct, id: string, props:FrontEndProps) {
|
||||
export class Web extends Construct {
|
||||
readonly distribution;
|
||||
constructor(scope: Construct, id: string, props:WebProps) {
|
||||
super(scope, id)
|
||||
|
||||
const commonBucketProps: s3.BucketProps = {
|
||||
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
|
||||
encryption: s3.BucketEncryption.S3_MANAGED,
|
||||
autoDeleteObjects: true,
|
||||
removalPolicy: RemovalPolicy.DESTROY,
|
||||
objectOwnership: s3.ObjectOwnership.OBJECT_WRITER,
|
||||
enforceSSL: true,
|
||||
};
|
||||
|
||||
const containerPort = 3000
|
||||
const frontendTaskDefinition = new ecs.FargateTaskDefinition(
|
||||
this,
|
||||
'FrontendTaskDef',
|
||||
{
|
||||
memoryLimitMiB: 3072,
|
||||
cpu: 1024,
|
||||
executionRole: props.frontendTaskExecutionRole,
|
||||
runtimePlatform:{
|
||||
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
|
||||
cpuArchitecture: props.arch,
|
||||
},
|
||||
taskRole: props.frontendTaskRole,
|
||||
}
|
||||
// CDKにて 静的WebサイトをホストするためのAmazon S3バケットを作成
|
||||
const websiteBucket = new s3.Bucket(this, 'LangflowWebsiteBucket', commonBucketProps);
|
||||
|
||||
const originAccessIdentity = new cloudfront.OriginAccessIdentity(
|
||||
this,
|
||||
'OriginAccessIdentity',
|
||||
{
|
||||
comment: 'langflow-distribution-originAccessIdentity',
|
||||
}
|
||||
);
|
||||
const frontendServiceName = 'frontend'
|
||||
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: [
|
||||
{
|
||||
name:frontendServiceName,
|
||||
containerPort: containerPort,
|
||||
protocol: ecs.Protocol.TCP,
|
||||
appProtocol:ecs.AppProtocol.http,
|
||||
},
|
||||
],
|
||||
|
||||
const webSiteBucketPolicyStatement = new iam.PolicyStatement({
|
||||
actions: ['s3:GetObject'],
|
||||
effect: iam.Effect.ALLOW,
|
||||
principals: [
|
||||
new iam.CanonicalUserPrincipal(
|
||||
originAccessIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId
|
||||
),
|
||||
],
|
||||
resources: [`${websiteBucket.bucketArn}/*`],
|
||||
});
|
||||
const frontendService = new ecs.FargateService(
|
||||
this,
|
||||
'FrontendService',
|
||||
{
|
||||
serviceName: frontendServiceName,
|
||||
cluster: props.cluster,
|
||||
desiredCount: 1,
|
||||
assignPublicIp: false,
|
||||
taskDefinition: frontendTaskDefinition,
|
||||
enableExecuteCommand: true,
|
||||
securityGroups: [props.ecsFrontSG],
|
||||
cloudMapOptions: {
|
||||
cloudMapNamespace: props.cloudmapNamespace,
|
||||
containerPort: containerPort,
|
||||
dnsRecordType: servicediscovery.DnsRecordType.A,
|
||||
dnsTtl: Duration.seconds(10),
|
||||
name: frontendServiceName
|
||||
},
|
||||
healthCheckGracePeriod: Duration.seconds(1000),
|
||||
}
|
||||
);
|
||||
|
||||
props.targetGroup.addTarget(frontendService);
|
||||
websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement);
|
||||
websiteBucket.grantRead(originAccessIdentity);
|
||||
|
||||
// // 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,
|
||||
// }
|
||||
// );
|
||||
const s3SpaOrigin = new origins.S3Origin(websiteBucket);
|
||||
const ApiSpaOrigin = new origins.LoadBalancerV2Origin(props.alb,{
|
||||
protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY
|
||||
});
|
||||
|
||||
const albBehaviorOptions = {
|
||||
origin: ApiSpaOrigin,
|
||||
allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
|
||||
|
||||
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.ALLOW_ALL,
|
||||
cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
|
||||
originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER
|
||||
}
|
||||
|
||||
const cloudFrontWebDistribution = new cloudfront.Distribution(this, 'distribution', {
|
||||
comment: 'langflow-distribution',
|
||||
defaultRootObject: 'index.html',
|
||||
errorResponses: [
|
||||
{
|
||||
httpStatus: 403,
|
||||
responseHttpStatus: 200,
|
||||
responsePagePath: '/index.html',
|
||||
},
|
||||
{
|
||||
httpStatus: 404,
|
||||
responseHttpStatus: 200,
|
||||
responsePagePath: '/index.html',
|
||||
},
|
||||
],
|
||||
defaultBehavior: { origin: s3SpaOrigin },
|
||||
additionalBehaviors: {
|
||||
'/api/v1/*': albBehaviorOptions,
|
||||
'/health' : albBehaviorOptions,
|
||||
},
|
||||
enableLogging: true, // ログ出力設定
|
||||
logBucket: new s3.Bucket(this, 'LogBucket',commonBucketProps),
|
||||
logFilePrefix: 'distribution-access-logs/',
|
||||
logIncludesCookies: true,
|
||||
});
|
||||
this.distribution = cloudFrontWebDistribution;
|
||||
|
||||
|
||||
new NodejsBuild(this, 'BuildFrontEnd', {
|
||||
assets: [
|
||||
{
|
||||
path: '../../src/frontend',
|
||||
exclude: [
|
||||
'.git',
|
||||
'.github',
|
||||
'.gitignore',
|
||||
'.prettierignore',
|
||||
'build',
|
||||
'node_modules'
|
||||
],
|
||||
},
|
||||
],
|
||||
nodejsVersion:20,
|
||||
destinationBucket: websiteBucket,
|
||||
distribution: cloudFrontWebDistribution,
|
||||
outputSourceDirectory: 'build',
|
||||
buildCommands: ['npm install', 'npm run build'],
|
||||
buildEnvironment: {
|
||||
// VITE_AXIOS_BASE_URL: `https://${this.distribution.domainName}`
|
||||
},
|
||||
});
|
||||
|
||||
// distribution から backendへのinbound 許可
|
||||
const alb_listen_port=80
|
||||
props.albSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(alb_listen_port))
|
||||
const alb_listen_port_443=443
|
||||
props.albSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(alb_listen_port_443))
|
||||
|
||||
|
||||
new CfnOutput(this, 'URL', {
|
||||
value: `https://${this.distribution.domainName}`,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -10,8 +10,6 @@ interface IAMProps {
|
|||
}
|
||||
|
||||
export class EcsIAM extends Construct {
|
||||
readonly frontendTaskRole: iam.Role;
|
||||
readonly frontendTaskExecutionRole: iam.Role;
|
||||
readonly backendTaskRole: iam.Role;
|
||||
readonly backendTaskExecutionRole: iam.Role;
|
||||
|
||||
|
|
@ -58,12 +56,6 @@ export class EcsIAM extends Construct {
|
|||
})],
|
||||
})
|
||||
|
||||
// FrontEnd Task Role
|
||||
this.frontendTaskRole = new iam.Role(this, 'FrontendTaskRole', {
|
||||
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
||||
});
|
||||
this.frontendTaskRole.addToPolicy(ECSExecPolicyStatement);
|
||||
|
||||
// BackEnd Task Role
|
||||
this.backendTaskRole = new iam.Role(this, 'BackendTaskRole', {
|
||||
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
||||
|
|
@ -73,17 +65,6 @@ export class EcsIAM extends Construct {
|
|||
// KendraとBedrockのアクセス権付与
|
||||
this.backendTaskRole.attachInlinePolicy(RagAccessPolicy);
|
||||
|
||||
// FrontEnd Task ExecutionRole
|
||||
this.frontendTaskExecutionRole = new iam.Role(this, 'frontendTaskExecutionRole', {
|
||||
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
||||
managedPolicies: [
|
||||
{
|
||||
managedPolicyArn:
|
||||
'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// BackEnd Task ExecutionRole
|
||||
this.backendTaskExecutionRole = new iam.Role(this, 'backendTaskExecutionRole', {
|
||||
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@ export * from './ecr';
|
|||
export * from './iam';
|
||||
export * from './frontend';
|
||||
export * from './backend';
|
||||
export * from './network';
|
||||
export * from './network';
|
||||
export * from './kendra';
|
||||
141
scripts/aws/lib/construct/kendra.ts
Normal file
141
scripts/aws/lib/construct/kendra.ts
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import * as kendra from 'aws-cdk-lib/aws-kendra';
|
||||
import * as iam from 'aws-cdk-lib/aws-iam';
|
||||
import { Construct } from 'constructs';
|
||||
import { Duration, Token, Arn } from 'aws-cdk-lib';
|
||||
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
|
||||
import { Runtime } from 'aws-cdk-lib/aws-lambda';
|
||||
|
||||
export interface RagProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* RAG を実行するためのリソースを作成する
|
||||
*/
|
||||
export class Rag extends Construct {
|
||||
constructor(scope: Construct, id: string, props: RagProps) {
|
||||
super(scope, id);
|
||||
|
||||
const kendraIndexArnInCdkContext =
|
||||
this.node.tryGetContext('kendraIndexArn');
|
||||
|
||||
let kendraIndexArn: string;
|
||||
let kendraIndexId: string;
|
||||
|
||||
if (kendraIndexArnInCdkContext) {
|
||||
// 既存の Kendra Index を利用する場合
|
||||
kendraIndexArn = kendraIndexArnInCdkContext!;
|
||||
kendraIndexId = Arn.extractResourceName(
|
||||
kendraIndexArnInCdkContext,
|
||||
'index'
|
||||
);
|
||||
} else {
|
||||
// 新規に Kendra Index を作成する場合
|
||||
const indexRole = new iam.Role(this, 'KendraIndexRole', {
|
||||
assumedBy: new iam.ServicePrincipal('kendra.amazonaws.com'),
|
||||
});
|
||||
|
||||
indexRole.addToPolicy(
|
||||
new iam.PolicyStatement({
|
||||
effect: iam.Effect.ALLOW,
|
||||
resources: ['*'],
|
||||
actions: ['s3:GetObject'],
|
||||
})
|
||||
);
|
||||
|
||||
indexRole.addManagedPolicy(
|
||||
iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLogsFullAccess')
|
||||
);
|
||||
|
||||
const index = new kendra.CfnIndex(this, 'KendraIndex', {
|
||||
name: 'langflow-index',
|
||||
edition: 'DEVELOPER_EDITION',
|
||||
roleArn: indexRole.roleArn,
|
||||
});
|
||||
|
||||
kendraIndexArn = Token.asString(index.getAtt('Arn'));
|
||||
kendraIndexId = index.ref;
|
||||
|
||||
// WebCrawler を作成
|
||||
const webCrawlerRole = new iam.Role(this, 'KendraWebCrawlerRole', {
|
||||
assumedBy: new iam.ServicePrincipal('kendra.amazonaws.com'),
|
||||
});
|
||||
webCrawlerRole.addToPolicy(
|
||||
new iam.PolicyStatement({
|
||||
effect: iam.Effect.ALLOW,
|
||||
resources: [kendraIndexArn],
|
||||
actions: ['kendra:BatchPutDocument', 'kendra:BatchDeleteDocument'],
|
||||
})
|
||||
);
|
||||
|
||||
new kendra.CfnDataSource(this, 'WebCrawler', {
|
||||
indexId: kendraIndexId,
|
||||
name: 'WebCrawler',
|
||||
type: 'WEBCRAWLER',
|
||||
roleArn: webCrawlerRole.roleArn,
|
||||
languageCode: 'ja',
|
||||
dataSourceConfiguration: {
|
||||
webCrawlerConfiguration: {
|
||||
urls: {
|
||||
seedUrlConfiguration: {
|
||||
webCrawlerMode: 'HOST_ONLY',
|
||||
// デモ用に AWS の GenAI 関連のページを取り込む
|
||||
seedUrls: [
|
||||
'https://aws.amazon.com/jp/what-is/generative-ai/',
|
||||
'https://aws.amazon.com/jp/generative-ai/',
|
||||
'https://aws.amazon.com/jp/generative-ai/use-cases/',
|
||||
'https://aws.amazon.com/jp/bedrock/',
|
||||
'https://aws.amazon.com/jp/bedrock/features/',
|
||||
'https://aws.amazon.com/jp/bedrock/testimonials/',
|
||||
],
|
||||
},
|
||||
},
|
||||
crawlDepth: 1,
|
||||
urlInclusionPatterns: ['https://aws.amazon.com/jp/.*'],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// RAG 関連の API を追加する
|
||||
// Lambda
|
||||
const queryFunction = new NodejsFunction(this, 'Query', {
|
||||
runtime: Runtime.NODEJS_18_X,
|
||||
entry: './lambda/queryKendra.ts',
|
||||
timeout: Duration.minutes(15),
|
||||
bundling: {
|
||||
// 新しい Kendra の機能を使うため、AWS SDK を明示的にバンドルする
|
||||
externalModules: [],
|
||||
},
|
||||
environment: {
|
||||
INDEX_ID: kendraIndexId,
|
||||
},
|
||||
});
|
||||
queryFunction.role?.addToPrincipalPolicy(
|
||||
new iam.PolicyStatement({
|
||||
effect: iam.Effect.ALLOW,
|
||||
resources: [kendraIndexArn],
|
||||
actions: ['kendra:Query'],
|
||||
})
|
||||
);
|
||||
|
||||
const retrieveFunction = new NodejsFunction(this, 'Retrieve', {
|
||||
runtime: Runtime.NODEJS_18_X,
|
||||
entry: './lambda/retrieveKendra.ts',
|
||||
timeout: Duration.minutes(15),
|
||||
bundling: {
|
||||
// 新しい Kendra の機能を使うため、AWS SDK を明示的にバンドルする
|
||||
externalModules: [],
|
||||
},
|
||||
environment: {
|
||||
INDEX_ID: kendraIndexId,
|
||||
},
|
||||
});
|
||||
retrieveFunction.role?.addToPrincipalPolicy(
|
||||
new iam.PolicyStatement({
|
||||
effect: iam.Effect.ALLOW,
|
||||
resources: [kendraIndexArn],
|
||||
actions: ['kendra:Retrieve'],
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,20 +11,16 @@ import {
|
|||
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 backendLogGroup: logs.LogGroup;
|
||||
readonly frontendLogGroup: logs.LogGroup;
|
||||
readonly alb: elb.IApplicationLoadBalancer;
|
||||
readonly albTG: elb.ApplicationTargetGroup;
|
||||
readonly albSG: ec2.SecurityGroup;
|
||||
|
||||
constructor(scope: Construct, id: string) {
|
||||
super(scope, id)
|
||||
const alb_listen_port=80
|
||||
const front_service_port=3000
|
||||
const back_service_port=7860
|
||||
|
||||
// VPC等リソースの作成
|
||||
|
|
@ -51,22 +47,6 @@ export class Network extends Construct {
|
|||
],
|
||||
natGateways: 1,
|
||||
})
|
||||
// Cluster
|
||||
this.cluster = new ecs.Cluster(this, 'EcsCluster', {
|
||||
clusterName: 'langflow-cluster',
|
||||
vpc: this.vpc,
|
||||
enableFargateCapacityProviders: true,
|
||||
});
|
||||
|
||||
// Private DNS
|
||||
this.cloudmapNamespace = new servicediscovery.PrivateDnsNamespace(
|
||||
this,
|
||||
'Namespace',
|
||||
{
|
||||
name: 'ecs-deploy.com',
|
||||
vpc: this.vpc,
|
||||
}
|
||||
);
|
||||
|
||||
// ALBに設定するセキュリティグループ
|
||||
this.albSG = new ec2.SecurityGroup(scope, 'ALBSecurityGroup', {
|
||||
|
|
@ -74,7 +54,6 @@ export class Network extends Construct {
|
|||
description: 'for alb',
|
||||
vpc: this.vpc,
|
||||
})
|
||||
this.albSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(alb_listen_port))
|
||||
|
||||
this.alb = new elb.ApplicationLoadBalancer(this,'langflow-alb',{
|
||||
internetFacing: true, //インターネットからのアクセスを許可するかどうか指定
|
||||
|
|
@ -85,8 +64,8 @@ export class Network extends Construct {
|
|||
|
||||
const listener = this.alb.addListener('Listener', { port: alb_listen_port });
|
||||
|
||||
this.targetGroup = listener.addTargets('targetGroup', {
|
||||
port: front_service_port,
|
||||
this.albTG = listener.addTargets('targetGroup', {
|
||||
port: back_service_port,
|
||||
protocol: elb.ApplicationProtocol.HTTP,
|
||||
healthCheck: {
|
||||
enabled: true,
|
||||
|
|
@ -99,13 +78,12 @@ export class Network extends Construct {
|
|||
},
|
||||
});
|
||||
|
||||
// ECS FrontEndに設定するセキュリティグループ
|
||||
this.ecsFrontSG = new ec2.SecurityGroup(scope, 'ECSFrontEndSecurityGroup', {
|
||||
securityGroupName: 'langflow-ecs-front-sg',
|
||||
description: 'for langflow-front-ecs',
|
||||
// Cluster
|
||||
this.cluster = new ecs.Cluster(this, 'EcsCluster', {
|
||||
clusterName: 'langflow-cluster',
|
||||
vpc: this.vpc,
|
||||
})
|
||||
this.ecsFrontSG.addIngressRule(this.albSG, ec2.Port.allTcp())
|
||||
enableFargateCapacityProviders: true,
|
||||
});
|
||||
|
||||
// ECS BackEndに設定するセキュリティグループ
|
||||
this.ecsBackSG = new ec2.SecurityGroup(scope, 'ECSBackEndSecurityGroup', {
|
||||
|
|
@ -113,7 +91,7 @@ export class Network extends Construct {
|
|||
description: 'for langflow-back-ecs',
|
||||
vpc: this.vpc,
|
||||
})
|
||||
this.ecsBackSG.addIngressRule(this.ecsFrontSG, ec2.Port.tcp(back_service_port))
|
||||
this.ecsBackSG.addIngressRule(this.albSG,ec2.Port.tcp(back_service_port))
|
||||
|
||||
// RDSに設定するセキュリティグループ
|
||||
this.dbSG = new ec2.SecurityGroup(scope, 'DBSecurityGroup', {
|
||||
|
|
@ -122,7 +100,7 @@ export class Network extends Construct {
|
|||
description: 'for langflow-db',
|
||||
vpc: this.vpc,
|
||||
})
|
||||
// AppRunnerSecurityGroupからのポート3306:mysql(5432:postgres)のインバウンドを許可
|
||||
// langflow-ecs-back-sg からのポート3306:mysql(5432:postgres)のインバウンドを許可
|
||||
this.dbSG.addIngressRule(this.ecsBackSG, ec2.Port.tcp(3306))
|
||||
|
||||
// Create CloudWatch Log Group
|
||||
|
|
@ -131,13 +109,5 @@ export class Network extends Construct {
|
|||
removalPolicy: RemovalPolicy.DESTROY,
|
||||
});
|
||||
|
||||
this.frontendLogGroup = new logs.LogGroup(this, 'frontendLogGroup', {
|
||||
logGroupName: 'langflow-frontend-logs',
|
||||
removalPolicy: RemovalPolicy.DESTROY,
|
||||
});
|
||||
|
||||
new CfnOutput(this, 'URL', {
|
||||
value: `http://${this.alb.loadBalancerDnsName}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue