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

Fix bug in deployment procedure in AWS CDK and support HTTPS #1491

Merged
merged 24 commits into from Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion cdk.Dockerfile
Expand Up @@ -15,6 +15,7 @@ COPY ./ ./
# Install dependencies
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi

RUN poetry add pymysql==1.0.2
RUN poetry add botocore
RUN poetry add pymysql

CMD ["sh", "./container-cmd-cdk.sh"]
4 changes: 3 additions & 1 deletion container-cmd-cdk.sh
@@ -1,3 +1,5 @@
export LANGFLOW_DATABASE_URL="mysql+pymysql://${username}:${password}@${host}:3306/${dbname}"
# echo $LANGFLOW_DATABASE_URL
uvicorn --factory src.backend.langflow.main:create_app --host 0.0.0.0 --port 7860 --reload --log-level debug
uvicorn --factory src.backend.langflow.main:create_app --host 0.0.0.0 --port 7860 --reload --log-level debug

# python -m langflow run --host 0.0.0.0 --port 7860
9 changes: 4 additions & 5 deletions scripts/aws/README.ja.md
Expand Up @@ -8,10 +8,9 @@ Langflow on AWS では、 [AWS Cloud Development Kit](https://aws.amazon.com/cdk

作成するアプリケーションのアーキテクチャです。
![langflow-archi](./img/langflow-archi.png)
AWS CDK によって [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/?nc1=h_ls)、[AWS Fargate](https://aws.amazon.com/fargate/?nc2=type_a)、[Amazon Aurora](https://aws.amazon.com/rds/aurora/?nc2=type_a) を作成します。
AWS CDK によって Langflow のアプリケーションをデプロイします。アプリケーションは [Amazon CloudFront](https://aws.amazon.com/cloudfront/?nc1=h_ls) を介して配信されます。CloudFront は 2 つのオリジンを有しています。1 つ目は静的な Web サイトを配信するための [Amazon Simple Storage Service](https://aws.amazon.com/s3/?nc1=h_ls) (S3)、2 つ目は バックエンドと通信するための [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/?nc1=h_ls) (ALB) です。ALB の背後には FastAPI が動作する [AWS Fargate](https://aws.amazon.com/fargate/?nc2=type_a) 、データベースの [Amazon Aurora](https://aws.amazon.com/rds/aurora/?nc2=type_a) が作成されます。
Fargate は [Amazon Elastic Container Registry](https://aws.amazon.com/ecr/?nc1=h_ls) (ECR) に保存された Docker イメージを使用します。
Auroraのシークレットは [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/?nc2=type_a) によって管理されます。
Fargate のタスクはフロントエンドとバックエンドに分かれており、サービス検出によって通信します。
リソースをデプロイするだけであれば、上記の各サービスについて深い知識は必要ありません。

# 環境構築とデプロイ方法
1. [AWS CloudShell](https://us-east-1.console.aws.amazon.com/cloudshell/home?region=us-east-1)を開きます。
Expand All @@ -28,7 +27,7 @@ Fargate のタスクはフロントエンドとバックエンドに分かれて

1. 以下のコマンドを実行します。
```shell
git clone -b aws-cdk https://github.com/logspace-ai/langflow.git
git clone https://github.com/logspace-ai/langflow.git
cd langflow/scripts/aws
cp .env.example .env # 環境設定を変える場合はこのファイル(.env)を編集してください。
npm ci
Expand All @@ -38,7 +37,7 @@ Fargate のタスクはフロントエンドとバックエンドに分かれて
1. 表示される URL にアクセスします。
```shell
Outputs:
LangflowAppStack.NetworkURLXXXXXX = http://alb-XXXXXXXXXXX.elb.amazonaws.com
LangflowAppStack.frontendURLXXXXXX = https://XXXXXXXXXXX.cloudfront.net
```
1. サインイン画面でユーザー名とパスワードを入力します。`.env`ファイルでユーザー名とパスワードを設定していない場合、ユーザー名は`admin`、パスワードは`123456`で設定されます。
![make-cloud9](./img/langflow-signin.png)
Expand Down
18 changes: 9 additions & 9 deletions scripts/aws/README.md
Expand Up @@ -9,11 +9,9 @@ This tutorial assumes you have an AWS account and basic knowledge of AWS.

The architecture of the application to be created:
![langflow-archi](./img/langflow-archi.png)

[Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/?nc1=h_ls), [AWS Fargate](https://aws.amazon.com/fargate/?nc2=type_a) and [Amazon Aurora](https://aws.amazon.com/rds/aurora/?nc2=type_a) are created by AWS CDK.
The aurora's secrets are managed by [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/?nc2=type_a).
The Fargate task is divided into a frontend and a backend, which communicate through service discovery.
If you just want to deploy resources, you do not need in-depth knowledge of each of the above services.
Langflow is deployed using AWS CDK. The application is distributed via [Amazon CloudFront](https://aws.amazon.com/cloudfront/?nc1=h_ls), which has two origins: the first is [Amazon Simple Storage Service](https://aws.amazon.com/s3/?nc1=h_ls) (S3) for serving a static website, and the second is an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/?nc1=h_ls) (ALB) for communicating with the backend. [AWS Fargate](https://aws.amazon.com/fargate/?nc2=type_a), where FastAPI runs and [Amazon Aurora](https://aws.amazon.com/rds/aurora/?nc2=type_a), the database, are created behind the ALB.
Fargate uses a Docker image stored in [Amazon Elastic Container Registry](https://aws.amazon.com/ecr/?nc1=h_ls) (ECR).
Aurora's secret is managed by [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/?nc2=type_a).

# How to set up your environment and deploy langflow

Expand All @@ -22,13 +20,14 @@ If you just want to deploy resources, you do not need in-depth knowledge of each
```shell
git clone https://github.com/aws-samples/cloud9-setup-for-prototyping
cd cloud9-setup-for-prototyping
cat params.json | jq '.name |= "c9-for-langflow"'
./bin/bootstrap
```
1. When you see `Done!` in Cloudshell, open `cloud9-for-prototyping` from [AWS Cloud9](https://us-east-1.console.aws.amazon.com/cloud9control/home?region=us-east-1#/).
1. When you see `Done!` in Cloudshell, open `c9-for-langflow` from [AWS Cloud9](https://us-east-1.console.aws.amazon.com/cloud9control/home?region=us-east-1#/).
![make-cloud9](./img/langflow-cloud9-en.png)
1. Run the following command in the Cloud9 terminal.
```shell
git clone -b aws-cdk https://github.com/logspace-ai/langflow.git
git clone https://github.com/logspace-ai/langflow.git
cd langflow/scripts/aws
cp .env.example .env # Edit this file if you need environment settings
npm ci
Expand All @@ -38,7 +37,7 @@ If you just want to deploy resources, you do not need in-depth knowledge of each
1. Access the URL displayed.
```shell
Outputs:
LangflowAppStack.NetworkURLXXXXXX = http://alb-XXXXXXXXXXX.elb.amazonaws.com
LangflowAppStack.frontendURLXXXXXX = https://XXXXXXXXXXX.cloudfront.net
```
1. Enter your user name and password to sign in. If you have not set a user name and password in your `.env` file, the user name will be set to `admin` and the password to `123456`.
![signin-langflow](./img/langflow-signin.png)
Expand All @@ -49,5 +48,6 @@ If you just want to deploy resources, you do not need in-depth knowledge of each
```shell
bash delete-resources.sh
```
1. Open [AWS CloudFormation](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/getting-started), select `aws-cloud9-cloud9-for-prototyping-XXXX` and delete it.
1. Open [AWS CloudFormation](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/getting-started), select `aws-cloud9-c9-for-langflow-XXXX` and delete it.
![delete-cfn](./img/langflow-cfn.png)
s
1 change: 1 addition & 0 deletions scripts/aws/bin/cdk.ts
Expand Up @@ -4,6 +4,7 @@ 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,
Expand Down
2 changes: 2 additions & 0 deletions scripts/aws/cdk.json
Expand Up @@ -17,6 +17,8 @@
]
},
"context": {
"ragEnabled": false,
"kendraIndexArn": null,
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
Expand Down
4 changes: 2 additions & 2 deletions scripts/aws/delete-resources.sh
@@ -1,4 +1,4 @@
aws cloudformation delete-stack --stack-name LangflowAppStack
# 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 delete-repository --repository-name langflow-frontend-repository --force
# aws ecr describe-repositories --output json | jq -re ".repositories[].repositoryName"
Binary file modified scripts/aws/img/langflow-archi.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 27 additions & 21 deletions scripts/aws/lib/cdk-stack.ts
Expand Up @@ -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
})

Expand All @@ -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
})

Expand All @@ -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')
}
}
29 changes: 7 additions & 22 deletions scripts/aws/lib/construct/backend.ts
Expand Up @@ -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!;

Expand All @@ -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,
},
],
Expand All @@ -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);
}
}
26 changes: 1 addition & 25 deletions scripts/aws/lib/construct/ecr.ts
Expand Up @@ -9,65 +9,41 @@ 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,
})
// 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",
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),
Expand Down