-
Notifications
You must be signed in to change notification settings - Fork 0
/
cdk-varco-opensearch-stack.ts
351 lines (326 loc) · 12.4 KB
/
cdk-varco-opensearch-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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as path from "path";
import * as logs from "aws-cdk-lib/aws-logs"
import * as iam from 'aws-cdk-lib/aws-iam';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as cloudFront from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
import * as apiGateway from 'aws-cdk-lib/aws-apigateway';
import * as s3Deploy from "aws-cdk-lib/aws-s3-deployment";
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as opensearch from 'aws-cdk-lib/aws-opensearchservice';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
const region = process.env.CDK_DEFAULT_REGION;
const debug = false;
const stage = 'dev';
const s3_prefix = 'docs';
const projectName = `chatbot-varco-os-${region}`;
const bucketName = `storage-for-${projectName}`;
const endpoint_name = 'endpoint-varco-llm-ko-13b-ist-1';
const varco_region = "us-west-2";
const opensearch_account = "admin";
const opensearch_passwd = "Wifi1234!";
const embedding_region = "us-west-2";
const endpoint_embedding = 'jumpstart-dft-hf-textembedding-gpt-j-6b-fp16';
const enableReference = 'false';
const enableRAG = 'true';
export class CdkVarcoOpensearchStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// s3
const s3Bucket = new s3.Bucket(this, `storage-${projectName}`,{
bucketName: bucketName,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
publicReadAccess: false,
versioned: false,
cors: [
{
allowedHeaders: ['*'],
allowedMethods: [
s3.HttpMethods.POST,
s3.HttpMethods.PUT,
],
allowedOrigins: ['*'],
},
],
});
if(debug) {
new cdk.CfnOutput(this, 'bucketName', {
value: s3Bucket.bucketName,
description: 'The nmae of bucket',
});
new cdk.CfnOutput(this, 's3Arn', {
value: s3Bucket.bucketArn,
description: 'The arn of s3',
});
new cdk.CfnOutput(this, 's3Path', {
value: 's3://'+s3Bucket.bucketName,
description: 'The path of s3',
});
}
// DynamoDB for call log
const callLogTableName = `db-call-log-for-${projectName}`;
const callLogDataTable = new dynamodb.Table(this, `db-call-log-for-${projectName}`, {
tableName: callLogTableName,
partitionKey: { name: 'user-id', type: dynamodb.AttributeType.STRING },
sortKey: { name: 'request-id', type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
const callLogIndexName = `index-type-for-${projectName}`;
callLogDataTable.addGlobalSecondaryIndex({ // GSI
indexName: callLogIndexName,
partitionKey: { name: 'type', type: dynamodb.AttributeType.STRING },
});
// copy web application files into s3 bucket
new s3Deploy.BucketDeployment(this, `upload-HTML-for-${projectName}`, {
sources: [s3Deploy.Source.asset("../html")],
destinationBucket: s3Bucket,
});
// Permission for OpenSearch
const domainName = `${projectName}`
const accountId = process.env.CDK_DEFAULT_ACCOUNT;
const resourceArn = `arn:aws:es:${region}:${accountId}:domain/${domainName}/*`
if(debug) {
new cdk.CfnOutput(this, `resource-arn-for-${projectName}`, {
value: resourceArn,
description: 'The arn of resource',
});
}
const OpenSearchPolicy = new iam.PolicyStatement({
resources: [resourceArn],
actions: ['es:*'],
});
const OpenSearchAccessPolicy = new iam.PolicyStatement({
resources: [resourceArn],
actions: ['es:*'],
effect: iam.Effect.ALLOW,
principals: [new iam.AnyPrincipal()],
});
// OpenSearch
let opensearch_url = "";
const domain = new opensearch.Domain(this, 'Domain', {
version: opensearch.EngineVersion.OPENSEARCH_2_3,
domainName: domainName,
removalPolicy: cdk.RemovalPolicy.DESTROY,
enforceHttps: true,
fineGrainedAccessControl: {
masterUserName: opensearch_account,
// masterUserPassword: cdk.SecretValue.secretsManager('opensearch-private-key'),
masterUserPassword:cdk.SecretValue.unsafePlainText(opensearch_passwd)
},
capacity: {
masterNodes: 3,
masterNodeInstanceType: 'm6g.large.search',
// multiAzWithStandbyEnabled: false,
dataNodes: 3,
dataNodeInstanceType: 'r6g.large.search',
// warmNodes: 2,
// warmInstanceType: 'ultrawarm1.medium.search',
},
accessPolicies: [OpenSearchAccessPolicy],
ebs: {
volumeSize: 100,
volumeType: ec2.EbsDeviceVolumeType.GP3,
},
nodeToNodeEncryption: true,
encryptionAtRest: {
enabled: true,
},
zoneAwareness: {
enabled: true,
availabilityZoneCount: 3,
}
});
new cdk.CfnOutput(this, `Domain-of-OpenSearch-for-${projectName}`, {
value: domain.domainArn,
description: 'The arm of OpenSearch Domain',
});
new cdk.CfnOutput(this, `Endpoint-of-OpenSearch-for-${projectName}`, {
value: 'https://'+domain.domainEndpoint,
description: 'The endpoint of OpenSearch Domain',
});
opensearch_url = 'https://'+domain.domainEndpoint;
// cloudfront
const distribution = new cloudFront.Distribution(this, `cloudfront-for-${projectName}`, {
defaultBehavior: {
origin: new origins.S3Origin(s3Bucket),
allowedMethods: cloudFront.AllowedMethods.ALLOW_ALL,
cachePolicy: cloudFront.CachePolicy.CACHING_DISABLED,
viewerProtocolPolicy: cloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
priceClass: cloudFront.PriceClass.PRICE_CLASS_200,
});
new cdk.CfnOutput(this, `distributionDomainName-for-${projectName}`, {
value: distribution.domainName,
description: 'The domain name of the Distribution',
});
// role for lambda chat
const roleLambda = new iam.Role(this, `role-lambda-chat-for-${projectName}`, {
roleName: `role-lambda-chat-for-${projectName}`,
assumedBy: new iam.CompositePrincipal(
new iam.ServicePrincipal("lambda.amazonaws.com")
)
});
roleLambda.addManagedPolicy({
managedPolicyArn: 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole',
});
roleLambda.attachInlinePolicy( // add opensearch policy
new iam.Policy(this, `opensearch-policy-for-${projectName}`, {
statements: [OpenSearchPolicy],
}),
);
// Lambda for chat
const lambdaChatApi = new lambda.DockerImageFunction(this, `lambda-chat-for-${projectName}`, {
description: 'lambda for chat api',
functionName: `lambda-chat-api-for-${projectName}`,
code: lambda.DockerImageCode.fromImageAsset(path.join(__dirname, '../../lambda-chat')),
timeout: cdk.Duration.seconds(60),
memorySize: 4096,
role: roleLambda,
environment: {
opensearch_url: opensearch_url,
s3_bucket: s3Bucket.bucketName,
s3_prefix: s3_prefix,
callLogTableName: callLogTableName,
varco_region: varco_region,
endpoint_name: endpoint_name,
opensearch_account: opensearch_account,
opensearch_passwd: opensearch_passwd,
embedding_region: embedding_region,
endpoint_embedding: endpoint_embedding,
enableReference: enableReference,
enableRAG: enableRAG
}
});
lambdaChatApi.grantInvoke(new iam.ServicePrincipal('apigateway.amazonaws.com'));
s3Bucket.grantRead(lambdaChatApi); // permission for s3
callLogDataTable.grantReadWriteData(lambdaChatApi); // permission for dynamo
const SageMakerPolicy = new iam.PolicyStatement({ // policy statement for sagemaker
actions: ['sagemaker:*'],
resources: ['*'],
});
lambdaChatApi.role?.attachInlinePolicy( // add sagemaker policy
new iam.Policy(this, `sagemaker-policy-for-${projectName}`, {
statements: [SageMakerPolicy],
}),
);
// role for API Gateway
const role = new iam.Role(this, `api-role-for-${projectName}`, {
roleName: `api-role-for-${projectName}`,
assumedBy: new iam.ServicePrincipal("apigateway.amazonaws.com")
});
role.addToPolicy(new iam.PolicyStatement({
resources: ['*'],
actions: ['lambda:InvokeFunction']
}));
role.addManagedPolicy({
managedPolicyArn: 'arn:aws:iam::aws:policy/AWSLambdaExecute',
});
// API Gateway
const api = new apiGateway.RestApi(this, `api-chatbot-for-${projectName}`, {
description: 'API Gateway for chatbot',
endpointTypes: [apiGateway.EndpointType.REGIONAL],
binaryMediaTypes: ['application/pdf', 'text/plain', 'text/csv'],
deployOptions: {
stageName: stage,
// logging for debug
// loggingLevel: apiGateway.MethodLoggingLevel.INFO,
// dataTraceEnabled: true,
},
});
// POST method
const chat = api.root.addResource('chat');
chat.addMethod('POST', new apiGateway.LambdaIntegration(lambdaChatApi, {
passthroughBehavior: apiGateway.PassthroughBehavior.WHEN_NO_TEMPLATES,
credentialsRole: role,
integrationResponses: [{
statusCode: '200',
}],
proxy:false,
}), {
methodResponses: [ // API Gateway sends to the client that called a method.
{
statusCode: '200',
responseModels: {
'application/json': apiGateway.Model.EMPTY_MODEL,
},
}
]
});
if(debug) {
new cdk.CfnOutput(this, `apiUrl-chat-for-${projectName}`, {
value: api.url,
description: 'The url of API Gateway',
});
new cdk.CfnOutput(this, `curlUrl-chat-for-${projectName}`, {
value: "curl -X POST "+api.url+'chat -H "Content-Type: application/json" -d \'{"text":"who are u?"}\'',
description: 'Curl commend of API Gateway',
});
}
// cloudfront setting for api gateway of stable diffusion
distribution.addBehavior("/chat", new origins.RestApiOrigin(api), {
cachePolicy: cloudFront.CachePolicy.CACHING_DISABLED,
allowedMethods: cloudFront.AllowedMethods.ALLOW_ALL,
viewerProtocolPolicy: cloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
});
new cdk.CfnOutput(this, `WebUrl-for-${projectName}`, {
value: 'https://'+distribution.domainName+'/chat.html',
description: 'The web url of request for chat',
});
new cdk.CfnOutput(this, `UpdateCommend-for-${projectName}`, {
value: 'aws s3 cp ../html/chat.js '+'s3://'+s3Bucket.bucketName,
description: 'The url of web file upload',
});
// Lambda - Upload
const lambdaUpload = new lambda.Function(this, `lambda-upload-for-${projectName}`, {
runtime: lambda.Runtime.NODEJS_16_X,
functionName: `lambda-upload-for-${projectName}`,
code: lambda.Code.fromAsset("../lambda-upload"),
handler: "index.handler",
timeout: cdk.Duration.seconds(10),
logRetention: logs.RetentionDays.ONE_DAY,
environment: {
bucketName: s3Bucket.bucketName,
s3_prefix: s3_prefix
}
});
s3Bucket.grantReadWrite(lambdaUpload);
// POST method - upload
const resourceName = "upload";
const upload = api.root.addResource(resourceName);
upload.addMethod('POST', new apiGateway.LambdaIntegration(lambdaUpload, {
passthroughBehavior: apiGateway.PassthroughBehavior.WHEN_NO_TEMPLATES,
credentialsRole: role,
integrationResponses: [{
statusCode: '200',
}],
proxy:false,
}), {
methodResponses: [
{
statusCode: '200',
responseModels: {
'application/json': apiGateway.Model.EMPTY_MODEL,
},
}
]
});
if(debug) {
new cdk.CfnOutput(this, `ApiGatewayUrl-for-${projectName}`, {
value: api.url+'upload',
description: 'The url of API Gateway',
});
}
// cloudfront setting for api gateway
distribution.addBehavior("/upload", new origins.RestApiOrigin(api), {
cachePolicy: cloudFront.CachePolicy.CACHING_DISABLED,
allowedMethods: cloudFront.AllowedMethods.ALLOW_ALL,
viewerProtocolPolicy: cloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
});
}
}