Add procedure to deploy langflow on AWS (#1177)

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-12-07 10:52:11 -03:00 • committed by GitHub
commit b2d867e9aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 5647 additions and 29 deletions

View file

@ -1 +1,2 @@
.venv/
.venv/
**/aws

20
cdk.Dockerfile Normal file
View file

@ -0,0 +1,20 @@
FROM --platform=linux/amd64 python:3.10-slim
WORKDIR /app
# Install Poetry
RUN apt-get update && apt-get install gcc g++ curl build-essential postgresql-server-dev-all -y
RUN curl -sSL https://install.python-poetry.org | python3 -
# # Add Poetry to PATH
ENV PATH="${PATH}:/root/.local/bin"
# # Copy the pyproject.toml and poetry.lock files
COPY poetry.lock pyproject.toml ./
# Copy the rest of the application codes
COPY ./ ./
# Install dependencies
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi
RUN poetry add pymysql==1.0.2
CMD ["uvicorn", "--factory", "src.backend.langflow.main:create_app", "--host", "0.0.0.0", "--port", "7860", "--reload", "--log-level", "debug"]

View file

@ -1,28 +1,35 @@
version: "3"
networks:
langflow:
services:
backend:
build:
context: ./
dockerfile: ./dev.Dockerfile
dockerfile: ./cdk.Dockerfile
env_file:
- .env
ports:
- "7860:7860"
volumes:
- ./:/app
command: bash -c "uvicorn --factory src.backend.langflow.main:create_app --host 0.0.0.0 --port 7860 --reload"
networks:
- langflow
frontend:
build:
context: ./src/frontend
dockerfile: ./dev.Dockerfile
dockerfile: ./cdk.Dockerfile
args:
- BACKEND_URL=http://backend:7860
environment:
- VITE_PROXY_TARGET=http://backend:7860
ports:
- "3000:3000"
- "8080:3000"
volumes:
- ./src/frontend/public:/home/node/app/public
- ./src/frontend/src:/home/node/app/src
- ./src/frontend/package.json:/home/node/app/package.json
restart: on-failure
networks:
- langflow

11
scripts/aws/.env.example Normal file
View file

@ -0,0 +1,11 @@
# Description: Example of .env file
# Usage: Copy this file to .env and change the values
# according to your needs
# Do not commit .env file to git
# Do not change .env.example file
#     You can set up a superuser's username and password
# If there is no need for user management, set LANGFLOW_AUTO_LOGIN=true and delete LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD.
LANGFLOW_AUTO_LOGIN=false
LANGFLOW_SUPERUSER=admin
LANGFLOW_SUPERUSER_PASSWORD=123456

9
scripts/aws/.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
*.js
!jest.config.js
*.d.ts
node_modules
# CDK asset staging directory
.cdk.staging
cdk.out
!/lib

6
scripts/aws/.npmignore Normal file
View file

@ -0,0 +1,6 @@
*.ts
!*.d.ts
# CDK asset staging directory
.cdk.staging
cdk.out

54
scripts/aws/README.ja.md Normal file
View file

@ -0,0 +1,54 @@
# Langflow on AWS
**想定時間**: 30 分
## 説明
Langflow on AWS では、 [AWS Cloud Development Kit](https://aws.amazon.com/cdk/?nc2=type_a) (CDK) を用いて Langflow を AWS 上にデプロイする方法を学べます。
このチュートリアルは、AWS アカウントと AWS に関する基本的な知識を有していることを前提としています。
作成するアプリケーションのアーキテクチャです。
![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) を作成します。
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)を開きます。
1. 以下のコマンドを実行します。
```shell
git clone https://github.com/aws-samples/cloud9-setup-for-prototyping
cd cloud9-setup-for-prototyping
./bin/bootstrap
```
1. `Done!` と表示されたら [AWS Cloud9](https://us-east-1.console.aws.amazon.com/cloud9control/home?region=us-east-1#/) から `cloud9-for-prototyping` を開きます。
![make-cloud9](./img/langflow-cloud9.png)
1. 以下のコマンドを実行します。
```shell
git clone -b aws-cdk-dev2 https://github.com/kazuki306/langflow
cd langflow/scripts/aws
cp .env.example .env # 環境設定を変える場合はこのファイル(.env)を編集してください。
npm ci
cdk bootstrap
cdk deploy
```
1. 表示される URL にアクセスします。
```shell
Outputs:
LangflowAppStack.NetworkURLXXXXXX = http://alb-XXXXXXXXXXX.elb.amazonaws.com
```
1. サインイン画面でユーザー名とパスワードを入力します。`.env`ファイルでユーザー名とパスワードを設定していない場合、ユーザー名は`admin`、パスワードは`123456`で設定されます。
![make-cloud9](./img/langflow-signin.png)
# 環境の削除
1. `Cloud9` で以下のコマンドを実行します。
```shell
bash delete-resources.sh
```
1. [AWS CloudFormation](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/getting-started)を開き、`aws-cloud9-cloud9-for-prototyping-XXXX` を選択して削除します。
![delete-cfn](./img/langflow-cfn.png)

50
scripts/aws/README.md Normal file
View file

@ -0,0 +1,50 @@
# Deploy Langflow on AWS
**Duraration**: 30 minutes
## Introduction
In this tutorial, you will learn how to deploy langflow on AWS using [AWS Cloud Development Kit](https://aws.amazon.com/cdk/?nc2=type_a) (CDK).
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.
# How to set up your environment and deploy langflow
1. Open [AWS CloudShell](https://us-east-1.console.aws.amazon.com/cloudshell/home?region=us-east-1).
1. Run the following commands in Cloudshell:
```shell
git clone https://github.com/aws-samples/cloud9-setup-for-prototyping
cd cloud9-setup-for-prototyping
./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#/).
![make-cloud9](./img/langflow-cloud9-en.png)
1. Run the following command in the Cloud9 terminal.
```shell
git clone -b aws-cdk-dev2 https://github.com/kazuki306/langflow
cd langflow/scripts/aws
cp .env.example .env # Edit this file if you need environment settings
npm ci
cdk bootstrap
cdk deploy
```
1. Access the URL displayed.
```shell
Outputs:
LangflowAppStack.NetworkURLXXXXXX = http://alb-XXXXXXXXXXX.elb.amazonaws.com
```
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)
# Cleanup
1. Run the following command in the Cloud9 terminal.
```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.
![delete-cfn](./img/langflow-cfn.png)

21
scripts/aws/bin/cdk.ts Normal file
View 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
scripts/aws/cdk.json Normal file
View 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
}
}

View file

@ -0,0 +1,3 @@
docker stop $(docker ps -aq)
docker rm $(docker ps -aq)
docker rmi -f $(docker images -aq)

View 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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

View file

@ -0,0 +1,8 @@
module.exports = {
testEnvironment: 'node',
roots: ['<rootDir>/test'],
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.tsx?$': 'ts-jest'
}
};

View file

@ -0,0 +1,64 @@
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 * 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, 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 })
// IAM
const { frontendTaskRole, frontendTaskExecutionRole, backendTaskRole, backendTaskExecutionRole } = new EcsIAM(this, 'EcsIAM',{
rdsCluster:rdsCluster
})
const backendService = new BackEndCluster(this, 'backend', {
cluster:cluster,
ecsBackSG:ecsBackSG,
ecrBackEndRepository:ecrBackEndRepository,
backendTaskRole:backendTaskRole,
backendTaskExecutionRole:backendTaskExecutionRole,
backendLogGroup:backendLogGroup,
cloudmapNamespace:cloudmapNamespace,
rdsCluster:rdsCluster,
alb:alb,
arch:arch
})
backendService.node.addDependency(rdsCluster);
const frontendService = new FrontEndCluster(this, 'frontend',{
cluster:cluster,
ecsFrontSG:ecsFrontSG,
ecrFrontEndRepository:ecrFrontEndRepository,
targetGroup: targetGroup,
backendServiceName: backendService.backendServiceName,
frontendTaskRole: frontendTaskRole,
frontendTaskExecutionRole: frontendTaskExecutionRole,
frontendLogGroup: frontendLogGroup,
cloudmapNamespace: cloudmapNamespace,
arch:arch
})
frontendService.node.addDependency(backendService);
// S3+CloudFront
// new Web(this,'Cloudfront-S3')
}
}

View file

@ -0,0 +1,105 @@
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';
import * as dotenv from 'dotenv';
const path = require('path');
dotenv.config({path: path.join(__dirname, "../../.env")});
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: 3072,
cpu: 1024,
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://${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,
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 = 'backend'
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 },
});
}
}

View file

@ -0,0 +1,65 @@
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as rds from "aws-cdk-lib/aws-rds";
import * as cdk from 'aws-cdk-lib';
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',
})
}
}

Some files were not shown because too many files have changed in this diff Show more