Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cdk): Add multi-environment deployment support #778

Merged
merged 22 commits into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 105 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

[English](https://github.com/aws-samples/bedrock-claude-chat/blob/v2/README.md) | [日本語](https://github.com/aws-samples/bedrock-claude-chat/blob/v2/docs/README_ja-JP.md) | [한국어](https://github.com/aws-samples/bedrock-claude-chat/blob/v2/docs/README_ko-KR.md) | [中文](https://github.com/aws-samples/bedrock-claude-chat/blob/v2/docs/README_zh-CN.md) | [Français](https://github.com/aws-samples/bedrock-claude-chat/blob/v2/docs/README_fr-FR.md) | [Deutsch](https://github.com/aws-samples/bedrock-claude-chat/blob/v2/docs/README_de-DE.md) | [Español](https://github.com/aws-samples/bedrock-claude-chat/blob/v2/docs/README_es-ES.md) | [Italian](https://github.com/aws-samples/bedrock-claude-chat/blob/v2/docs/README_it-IT.md) | [Norsk](https://github.com/aws-samples/bedrock-claude-chat/blob/v2/docs/README_nb-NO.md) | [ไทย](https://github.com/aws-samples/bedrock-claude-chat/blob/v2/docs/README_th-TH.md) | [Bahasa Indonesia](https://github.com/aws-samples/bedrock-claude-chat/blob/v2/docs/README_id-ID.md) | [Bahasa Melayu](https://github.com/aws-samples/bedrock-claude-chat/blob/v2/docs/README_ms-MY.md) | [Tiếng Việt](https://github.com/aws-samples/bedrock-claude-chat/blob/v2/docs/README_vi-VN.md) | [Polski](https://github.com/aws-samples/bedrock-claude-chat/blob/v2/docs/README_pl-PL.md)

> [!Warning]
> [!Warning]
>
> **V2 released. To update, please carefully review the [migration guide](./docs/migration/V1_TO_V2.md).** Without any care, **BOTS FROM V1 WILL BECOME UNUSABLE.**

A multilingual chatbot using LLM models provided by [Amazon Bedrock](https://aws.amazon.com/bedrock/) for generative AI.
Expand Down Expand Up @@ -215,6 +216,109 @@ BedrockChatStack.BackendApiBackendApiUrlXXXXX = https://xxxxx.execute-api.ap-nor
BedrockChatStack.FrontendURL = https://xxxxx.cloudfront.net
```

### Defining Parameters

You can define parameters for your deployment in two ways: using `cdk.json` or using the type-safe `parameter.ts` file.

#### Using cdk.json (Traditional Method)

The traditional way to configure parameters is by editing the `cdk.json` file. This approach is simple but lacks type checking:

```json
{
"app": "npx ts-node --prefer-ts-exts bin/bedrock-chat.ts",
"context": {
"bedrockRegion": "us-east-1",
"allowedIpV4AddressRanges": ["0.0.0.0/1", "128.0.0.0/1"],
"enableMistral": false,
"selfSignUpEnabled": true
}
}
```

#### Using parameter.ts (Recommended Type-Safe Method)

For better type safety and developer experience, you can use the `parameter.ts` file to define your parameters:

```typescript
// Define parameters for the default environment
bedrockChatParams.set("default", {
bedrockRegion: "us-east-1",
allowedIpV4AddressRanges: ["192.168.0.0/16"],
enableMistral: false,
selfSignUpEnabled: true,
});

// Define parameters for additional environments
bedrockChatParams.set("dev", {
bedrockRegion: "us-west-2",
allowedIpV4AddressRanges: ["10.0.0.0/8"],
enableRagReplicas: false, // Cost-saving for dev environment
});

bedrockChatParams.set("prod", {
bedrockRegion: "us-east-1",
allowedIpV4AddressRanges: ["172.16.0.0/12"],
enableLambdaSnapStart: true,
enableRagReplicas: true, // Enhanced availability for production
});
```

> [!Note]
> Existing users can continue using `cdk.json` without any changes. The `parameter.ts` approach is recommended for new deployments or when you need to manage multiple environments.

### Deploying Multiple Environments

You can deploy multiple environments from the same codebase using the `parameter.ts` file and the `-c envName` option.

#### Prerequisites

1. Define your environments in `parameter.ts` as shown above
2. Each environment will have its own set of resources with environment-specific prefixes

#### Deployment Commands

To deploy a specific environment:

```bash
# Deploy the dev environment
npx cdk deploy --all -c envName=dev

# Deploy the prod environment
npx cdk deploy --all -c envName=prod
```

If no environment is specified, the "default" environment is used:

```bash
# Deploy the default environment
npx cdk deploy --all
```

#### Important Notes

1. **Stack Naming**:

- The main stacks for each environment will be prefixed with the environment name (e.g., `dev-BedrockChatStack`, `prod-BedrockChatStack`)
- However, custom bot stacks (`BrChatKbStack*`) and API publishing stacks (`ApiPublishmentStack*`) do not receive environment prefixes as they are created dynamically at runtime

2. **Resource Naming**:

- Only some resources receive environment prefixes in their names (e.g., `dev_ddb_export` table, `dev-FrontendWebAcl`)
- Most resources maintain their original names but are isolated by being in different stacks

3. **Environment Identification**:

- All resources are tagged with a `CDKEnvironment` tag containing the environment name
- You can use this tag to identify which environment a resource belongs to
- Example: `CDKEnvironment: dev` or `CDKEnvironment: prod`

4. **Default Environment Override**: If you define a "default" environment in `parameter.ts`, it will override the settings in `cdk.json`. To continue using `cdk.json`, don't define a "default" environment in `parameter.ts`.

5. **Environment Requirements**: To create environments other than "default", you must use `parameter.ts`. The `-c envName` option alone is not sufficient without corresponding environment definitions.

6. **Resource Isolation**: Each environment creates its own set of resources, allowing you to have development, testing, and production environments in the same AWS account without conflicts.

## Others

### Configure Mistral models support
Expand Down
5 changes: 4 additions & 1 deletion backend/app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ def store_api_key_to_secret_manager(
"""
secret_name = f"{prefix}/{user_id}/{bot_id}"
secret_value = json.dumps({"api_key": api_key})
env_name = os.environ.get("ENV_NAME", "default")

try:
secrets_client = boto3.client("secretsmanager")
Expand All @@ -216,7 +217,9 @@ def store_api_key_to_secret_manager(
# Create new secret if it doesn't exist
logger.info(f"Creating new secret: {secret_name}")
response = secrets_client.create_secret(
Name=secret_name, SecretString=secret_value
Name=secret_name,
SecretString=secret_value,
Tags=[{"Key": "CDKEnvironment", "Value": env_name}],
)
logger.info(f"Created new secret: {secret_name}")
return response["ARN"]
Expand Down
136 changes: 52 additions & 84 deletions cdk/bin/api-publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,103 +3,71 @@ import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { ApiPublishmentStack } from "../lib/api-publishment-stack";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import { resolveApiPublishParameters } from "../lib/utils/parameter-models";

const app = new cdk.App();

const BEDROCK_REGION = app.node.tryGetContext("bedrockRegion");
// Get parameters specific to API publishing
const params = resolveApiPublishParameters();
const sepHyphen = params.envPrefix ? "-" : "";

// Usage plan for the published API
const PUBLISHED_API_THROTTLE_RATE_LIMIT: number | undefined =
app.node.tryGetContext("publishedApiThrottleRateLimit")
? Number(app.node.tryGetContext("publishedApiThrottleRateLimit"))
: undefined;
const PUBLISHED_API_THROTTLE_BURST_LIMIT: number | undefined =
app.node.tryGetContext("publishedApiThrottleBurstLimit")
? Number(app.node.tryGetContext("publishedApiThrottleBurstLimit"))
: undefined;
const PUBLISHED_API_QUOTA_LIMIT: number | undefined = app.node.tryGetContext(
"publishedApiQuotaLimit"
)
? Number(app.node.tryGetContext("publishedApiQuotaLimit"))
: undefined;
const PUBLISHED_API_QUOTA_PERIOD: "DAY" | "WEEK" | "MONTH" | undefined =
app.node.tryGetContext("publishedApiQuotaPeriod")
? app.node.tryGetContext("publishedApiQuotaPeriod")
: undefined;
const PUBLISHED_API_DEPLOYMENT_STAGE = app.node.tryGetContext(
"publishedApiDeploymentStage"
);
const PUBLISHED_API_ID: string = app.node.tryGetContext("publishedApiId");
const PUBLISHED_API_ALLOWED_ORIGINS_STRING: string = app.node.tryGetContext(
"publishedApiAllowedOrigins"
);
const PUBLISHED_API_ALLOWED_ORIGINS: string[] = JSON.parse(
PUBLISHED_API_ALLOWED_ORIGINS_STRING || '["*"]'
// Parse allowed origins
const publishedApiAllowedOrigins = JSON.parse(
params.publishedApiAllowedOrigins || '["*"]'
);

console.log(
`PUBLISHED_API_THROTTLE_RATE_LIMIT: ${PUBLISHED_API_THROTTLE_RATE_LIMIT}`
);
console.log(
`PUBLISHED_API_THROTTLE_BURST_LIMIT: ${PUBLISHED_API_THROTTLE_BURST_LIMIT}`
);
console.log(`PUBLISHED_API_QUOTA_LIMIT: ${PUBLISHED_API_QUOTA_LIMIT}`);
console.log(`PUBLISHED_API_QUOTA_PERIOD: ${PUBLISHED_API_QUOTA_PERIOD}`);
console.log(
`PUBLISHED_API_DEPLOYMENT_STAGE: ${PUBLISHED_API_DEPLOYMENT_STAGE}`
);
console.log(`PUBLISHED_API_ID: ${PUBLISHED_API_ID}`);
console.log(`PUBLISHED_API_ALLOWED_ORIGINS: ${PUBLISHED_API_ALLOWED_ORIGINS}`);
// Log all parameters at once for debugging
console.log("API Publish Parameters:", JSON.stringify(params));

const webAclArn = cdk.Fn.importValue("PublishedApiWebAclArn");
const webAclArn = cdk.Fn.importValue(
`${params.envPrefix}${sepHyphen}PublishedApiWebAclArn`
);

const conversationTableName = cdk.Fn.importValue(
"BedrockClaudeChatConversationTableName"
`${params.envPrefix}${sepHyphen}BedrockClaudeChatConversationTableName`
);
const tableAccessRoleArn = cdk.Fn.importValue(
"BedrockClaudeChatTableAccessRoleArn"
`${params.envPrefix}${sepHyphen}BedrockClaudeChatTableAccessRoleArn`
);
const largeMessageBucketName = cdk.Fn.importValue(
"BedrockClaudeChatLargeMessageBucketName"
`${params.envPrefix}${sepHyphen}BedrockClaudeChatLargeMessageBucketName`
);

// NOTE: DO NOT change the stack id naming rule.
const publishedApi = new ApiPublishmentStack(
app,
`ApiPublishmentStack${PUBLISHED_API_ID}`,
{
env: {
region: process.env.CDK_DEFAULT_REGION,
},
bedrockRegion: BEDROCK_REGION,
conversationTableName: conversationTableName,
tableAccessRoleArn: tableAccessRoleArn,
webAclArn: webAclArn,
largeMessageBucketName: largeMessageBucketName,
usagePlan: {
throttle:
PUBLISHED_API_THROTTLE_RATE_LIMIT !== undefined &&
PUBLISHED_API_THROTTLE_BURST_LIMIT !== undefined
? {
rateLimit: PUBLISHED_API_THROTTLE_RATE_LIMIT,
burstLimit: PUBLISHED_API_THROTTLE_BURST_LIMIT,
}
: undefined,
quota:
PUBLISHED_API_QUOTA_LIMIT !== undefined &&
PUBLISHED_API_QUOTA_PERIOD !== undefined
? {
limit: PUBLISHED_API_QUOTA_LIMIT,
period: apigateway.Period[PUBLISHED_API_QUOTA_PERIOD],
}
: undefined,
},
deploymentStage: PUBLISHED_API_DEPLOYMENT_STAGE,
corsOptions: {
allowOrigins: PUBLISHED_API_ALLOWED_ORIGINS,
allowMethods: apigateway.Cors.ALL_METHODS,
allowHeaders: apigateway.Cors.DEFAULT_HEADERS,
allowCredentials: true,
},
}
);
new ApiPublishmentStack(app, `ApiPublishmentStack${params.publishedApiId}`, {
env: {
region: process.env.CDK_DEFAULT_REGION,
},
bedrockRegion: params.bedrockRegion,
conversationTableName: conversationTableName,
tableAccessRoleArn: tableAccessRoleArn,
webAclArn: webAclArn,
largeMessageBucketName: largeMessageBucketName,
usagePlan: {
throttle:
params.publishedApiThrottleRateLimit !== undefined &&
params.publishedApiThrottleBurstLimit !== undefined
? {
rateLimit: params.publishedApiThrottleRateLimit,
burstLimit: params.publishedApiThrottleBurstLimit,
}
: undefined,
quota:
params.publishedApiQuotaLimit !== undefined &&
params.publishedApiQuotaPeriod !== undefined
? {
limit: params.publishedApiQuotaLimit,
period: apigateway.Period[params.publishedApiQuotaPeriod],
}
: undefined,
},
deploymentStage: params.publishedApiDeploymentStage,
corsOptions: {
allowOrigins: publishedApiAllowedOrigins,
allowMethods: apigateway.Cors.ALL_METHODS,
allowHeaders: apigateway.Cors.DEFAULT_HEADERS,
allowCredentials: true,
},
});

cdk.Tags.of(app).add("CDKEnvironment", params.envName);
Loading
Loading