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

Feature/deployment easement stage 1 #162

Merged
merged 19 commits into from
Oct 29, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test-and-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches: ['main', 'develop', 'release/**', 'hotfix/**']
pull_request:
branches: ['main', 'develop', 'release/**', 'hotfix/**']
branches: ['main', 'develop', 'release/**', 'hotfix/**', 'feature/**']

permissions:
contents: read
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ coverage
/models
# Deployment configuration file
config.yaml
config-custom.yaml
61 changes: 32 additions & 29 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,50 +14,45 @@ HEADLESS = false

# Arguments defined through command line or config.yaml

# ENV
ifeq (${ENV},)
ENV := $(shell cat $(PROJECT_DIR)/config.yaml | yq '.env')
endif

ifeq (${ENV},)
$(error env must be set in command line using ENV variable or config.yaml)
endif

# PROFILE (optional argument)
ifeq (${PROFILE},)
TEMP_PROFILE := $(shell cat $(PROJECT_DIR)/config.yaml | yq .$(ENV).profile)
TEMP_PROFILE := $(shell cat $(PROJECT_DIR)/config-custom.yaml | yq .profile)
ifneq ($(TEMP_PROFILE), null)
PROFILE := ${TEMP_PROFILE}
else
$(warning profile is not set in the command line using PROFILE variable or config.yaml, attempting deployment without this variable)
$(warning profile is not set in the command line using PROFILE variable or config files, attempting deployment without this variable)
endif
endif

# DEPLOYMENT_NAME
ifeq (${DEPLOYMENT_NAME},)
DEPLOYMENT_NAME := $(shell cat $(PROJECT_DIR)/config.yaml | yq .$(ENV).deploymentName)
DEPLOYMENT_NAME := $(shell cat $(PROJECT_DIR)/config-custom.yaml | yq .deploymentName)
endif

ifeq (${DEPLOYMENT_NAME},)
$(error deploymentName must be set in command line using DEPLOYMENT_NAME variable or config.yaml)
ifeq (${DEPLOYMENT_NAME}, null)
DEPLOYMENT_NAME := $(shell cat $(PROJECT_DIR)/config-base.yaml | yq .deploymentName)
endif

ifeq (${DEPLOYMENT_NAME}, null)
$(error deploymentName must be set in command line using DEPLOYMENT_NAME variable or config files)
endif

# ACCOUNT_NUMBER
ifeq (${ACCOUNT_NUMBER},)
ACCOUNT_NUMBER := $(shell cat $(PROJECT_DIR)/config.yaml | yq .$(ENV).accountNumber)
ACCOUNT_NUMBER := $(shell cat $(PROJECT_DIR)/config-custom.yaml | yq .accountNumber)
endif

ifeq (${ACCOUNT_NUMBER},)
$(error accountNumber must be set in command line using ACCOUNT_NUMBER variable or config.yaml)
$(error accountNumber must be set in command line using ACCOUNT_NUMBER variable or config files)
endif

# REGION
ifeq (${REGION},)
REGION := $(shell cat $(PROJECT_DIR)/config.yaml | yq .$(ENV).region)
REGION := $(shell cat $(PROJECT_DIR)/config-custom.yaml | yq .region)
endif

ifeq (${REGION},)
$(error region must be set in command line using REGION variable or config.yaml)
$(error region must be set in command line using REGION variable or config files)
endif

# URL_SUFFIX - used for the docker login
Expand All @@ -67,22 +62,30 @@ else
URL_SUFFIX := c2s.ic.gov
endif

# Arguments defined through config.yaml
# Arguments defined through config files

# APP_NAME
APP_NAME := $(shell cat $(PROJECT_DIR)/config.yaml | yq .$(ENV).appName)
ifeq (${APP_NAME},)
$(error appName must be set in config.yaml)
APP_NAME := $(shell cat $(PROJECT_DIR)/config-custom.yaml | yq .appName)
ifeq (${APP_NAME}, null)
APP_NAME := $(shell cat $(PROJECT_DIR)/config-base.yaml | yq .appName)
endif

ifeq (${APP_NAME}, null)
APP_NAME := lisa
endif

# DEPLOYMENT_STAGE
DEPLOYMENT_STAGE := $(shell cat $(PROJECT_DIR)/config.yaml | yq .$(ENV).deploymentStage)
ifeq (${DEPLOYMENT_STAGE},)
$(error deploymentStage must be set in config.yaml)
DEPLOYMENT_STAGE := $(shell cat $(PROJECT_DIR)/config-custom.yaml | yq .deploymentStage)
ifeq (${DEPLOYMENT_STAGE}, null)
DEPLOYMENT_STAGE := $(shell cat $(PROJECT_DIR)/config-base.yaml | yq .deploymentStage)
endif

ifeq (${DEPLOYMENT_STAGE}, null)
$(error deploymentStage must be set in config files)
endif

# ACCOUNT_NUMBERS_ECR - AWS account numbers that need to be logged into with Docker CLI to use ECR
ACCOUNT_NUMBERS_ECR := $(shell cat $(PROJECT_DIR)/config.yaml | yq .$(ENV).accountNumbersEcr[])
ACCOUNT_NUMBERS_ECR := $(shell cat $(PROJECT_DIR)/config-custom.yaml | yq .accountNumbersEcr[])

# Append deployed account number to array for dockerLogin rule
ACCOUNT_NUMBERS_ECR := $(ACCOUNT_NUMBERS_ECR) $(ACCOUNT_NUMBER)
Expand All @@ -97,10 +100,10 @@ ifneq ($(findstring $(DEPLOYMENT_STAGE),$(STACK)),$(DEPLOYMENT_STAGE))
endif

# MODEL_IDS - IDs of models to deploy
MODEL_IDS := $(shell cat $(PROJECT_DIR)/config.yaml | yq '.$(ENV).ecsModels[].modelName')
MODEL_IDS := $(shell cat $(PROJECT_DIR)/config-custom.yaml | yq '.ecsModels[].modelName')

# MODEL_BUCKET - S3 bucket containing model artifacts
MODEL_BUCKET := $(shell cat $(PROJECT_DIR)/config.yaml | yq '.$(ENV).s3BucketModels')
MODEL_BUCKET := $(shell cat $(PROJECT_DIR)/config-custom.yaml | yq '.s3BucketModels')


#################################################################################
Expand Down Expand Up @@ -181,7 +184,7 @@ modelCheck:
echo "What is your huggingface access token? "; \
read -s access_token; \
echo "Converting and uploading safetensors for model: $(MODEL_ID)"; \
tgiImage=$$(yq -r '[.$(ENV).ecsModels[] | select(.inferenceContainer == "tgi") | .baseImage] | first' $(PROJECT_DIR)/config.yaml); \
tgiImage=$$(yq -r '[.ecsModels[] | select(.inferenceContainer == "tgi") | .baseImage] | first' $(PROJECT_DIR)/config-custom.yaml); \
echo $$tgiImage; \
$(PROJECT_DIR)/scripts/convert-and-upload-model.sh -m $(MODEL_ID) -s $(MODEL_BUCKET) -a $$access_token -t $$tgiImage -d $$localModelDir; \
fi; \
Expand Down
21 changes: 8 additions & 13 deletions bin/lisa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,17 @@ import * as path from 'path';

import * as cdk from 'aws-cdk-lib';
import * as yaml from 'js-yaml';
import _ from 'lodash';

import { Config, ConfigFile, ConfigSchema } from '../lib/schema';
import { LisaServeApplicationStage } from '../lib/stages';

// Read configuration file
const configFilePath = path.join(__dirname, '../config.yaml');
const configFile = yaml.load(fs.readFileSync(configFilePath, 'utf8')) as ConfigFile;
let configEnv = configFile.env || 'dev';

// Select configuration environment
if (process.env.ENV) {
configEnv = process.env.ENV;
}
const configData = configFile[configEnv];
if (!configData) {
throw new Error(`Configuration for environment "${configEnv}" not found.`);
}
// Read configuration files
const baseConfigFilePath = path.join(__dirname, '../config-base.yaml');
const customConfigFilePath = path.join(__dirname, '../config-custom.yaml');
const baseConfigFile = yaml.load(fs.readFileSync(baseConfigFilePath, 'utf8')) as ConfigFile;
const customConfigFile = yaml.load(fs.readFileSync(customConfigFilePath, 'utf8')) as ConfigFile;
const configData = _.merge(baseConfigFile, customConfigFile);

// Other command line argument overrides
type EnvMapping = [string, keyof Config];
Expand All @@ -59,6 +53,7 @@ mappings.forEach(([envVar, configVar]) => {
let config: Config;
try {
config = ConfigSchema.parse(configData);
console.log('MERGED CONFIG FILE:\n' + yaml.dump(config));
} catch (error) {
if (error instanceof Error) {
console.error('Error parsing the configuration:', error.message);
Expand Down
12 changes: 12 additions & 0 deletions config-base.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
mountS3DebUrl: https://s3.amazonaws.com/mountpoint-s3-release/latest/x86_64/mount-s3.deb
stackSynthesizer: CliCredentialsStackSynthesizer
ragRepositories:
- repositoryId: pgvector-rag
type: pgvector
rdsConfig:
username: postgres
ragFileProcessingConfig:
chunkSize: 512
chunkOverlap: 51
litellmConfig:
db_key: sk-a8814208-0388-480c-9fc7-fea59607ca38
6 changes: 3 additions & 3 deletions lib/api-base/authorizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import * as cdk from 'aws-cdk-lib';
import { RequestAuthorizer, IdentitySource } from 'aws-cdk-lib/aws-apigateway';
import { ISecurityGroup } from 'aws-cdk-lib/aws-ec2';
import { IRole } from 'aws-cdk-lib/aws-iam';
import { Code, Function, LayerVersion } from 'aws-cdk-lib/aws-lambda';
import { Code, Function, LayerVersion, Runtime } from 'aws-cdk-lib/aws-lambda';
import { StringParameter } from 'aws-cdk-lib/aws-ssm';
import { Construct } from 'constructs';

Expand Down Expand Up @@ -81,10 +81,10 @@ export class CustomAuthorizer extends Construct {
queueName: 'AuthorizerLambdaDLQ',
enforceSSL: true,
}),
runtime: config.lambdaConfig.pythonRuntime,
runtime: Runtime.PYTHON_3_10,
handler: 'authorizer.lambda_functions.lambda_handler',
functionName: `${cdk.Stack.of(this).stackName}-lambda-authorizer`,
code: Code.fromAsset(config.lambdaSourcePath),
code: Code.fromAsset('./lambda'),
description: 'REST API and UI Authorization Lambda',
timeout: cdk.Duration.seconds(30),
memorySize: 128,
Expand Down
70 changes: 51 additions & 19 deletions lib/api-base/fastApiContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { Construct } from 'constructs';
import { dump as yamlDump } from 'js-yaml';

import { ECSCluster } from './ecsCluster';
import { BaseProps, Ec2Metadata, EcsSourceType, FastApiContainerConfig } from '../schema';
import { BaseProps, Ec2Metadata, EcsSourceType } from '../schema';
import { Vpc } from '../networking/vpc';

// This is the amount of memory to buffer (or subtract off) from the total instance memory, if we don't include this,
Expand All @@ -35,13 +35,11 @@ const CONTAINER_MEMORY_BUFFER = 1024 * 2;
*
* @property {IVpc} vpc - The virtual private cloud (VPC).
* @property {SecurityGroup} securityGroups - The security groups of the application.
* @property {Map<number, ISubnet>} importedSubnets for application.
*/
type FastApiContainerProps = {
apiName: string;
resourcePath: string;
securityGroup: SecurityGroup;
taskConfig: FastApiContainerConfig;
tokenTable: ITable | undefined;
vpc: Vpc;
} & BaseProps;
Expand All @@ -67,23 +65,20 @@ export class FastApiContainer extends Construct {
constructor (scope: Construct, id: string, props: FastApiContainerProps) {
super(scope, id);

const { config, securityGroup, taskConfig, tokenTable, vpc } = props;
const { config, securityGroup, tokenTable, vpc } = props;

let buildArgs: Record<string, string> | undefined = undefined;
if (taskConfig.containerConfig.image.type === EcsSourceType.ASSET) {
buildArgs = {
BASE_IMAGE: taskConfig.containerConfig.image.baseImage,
PYPI_INDEX_URL: config.pypiConfig.indexUrl,
PYPI_TRUSTED_HOST: config.pypiConfig.trustedHost,
LITELLM_CONFIG: yamlDump(config.litellmConfig),
};
}
const buildArgs: Record<string, string> | undefined = {
BASE_IMAGE: 'python:3.10',
PYPI_INDEX_URL: config.pypiConfig.indexUrl,
PYPI_TRUSTED_HOST: config.pypiConfig.trustedHost,
LITELLM_CONFIG: yamlDump(config.litellmConfig),
};
const environment: Record<string, string> = {
LOG_LEVEL: config.logLevel,
AWS_REGION: config.region,
AWS_REGION_NAME: config.region, // for supporting SageMaker endpoints in LiteLLM
THREADS: Ec2Metadata.get(taskConfig.instanceType).vCpus.toString(),
LITELLM_KEY: config.litellmConfig.general_settings.master_key,
THREADS: Ec2Metadata.get('m5.large').vCpus.toString(),
LITELLM_KEY: config.litellmConfig.db_key,
};

if (config.restApiConfig.internetFacing) {
Expand All @@ -104,15 +99,52 @@ export class FastApiContainer extends Construct {
config,
ecsConfig: {
amiHardwareType: AmiHardwareType.STANDARD,
autoScalingConfig: taskConfig.autoScalingConfig,
autoScalingConfig: {
blockDeviceVolumeSize: 30,
minCapacity: 1,
maxCapacity: 1,
cooldown: 60,
defaultInstanceWarmup: 60,
metricConfig: {
AlbMetricName: 'RequestCountPerTarget',
targetValue: 1000,
duration: 60,
estimatedInstanceWarmup: 30
}
},
buildArgs,
containerConfig: taskConfig.containerConfig,
containerConfig: {
image: {
baseImage: 'python:3.10',
path: 'lib/serve/rest-api',
type: EcsSourceType.ASSET
},
healthCheckConfig: {
command: ['CMD-SHELL', 'exit 0'],
interval: 10,
startPeriod: 30,
timeout: 5,
retries: 3
},
environment: {},
sharedMemorySize: 0
},
containerMemoryBuffer: CONTAINER_MEMORY_BUFFER,
environment,
identifier: props.apiName,
instanceType: taskConfig.instanceType,
instanceType: 'm5.large',
internetFacing: config.restApiConfig.internetFacing,
loadBalancerConfig: taskConfig.loadBalancerConfig,
loadBalancerConfig: {
healthCheckConfig: {
path: '/health',
interval: 60,
timeout: 30,
healthyThresholdCount: 2,
unhealthyThresholdCount: 10
},
domainName: config.restApiConfig.domainName,
sslCertIamArn: config.restApiConfig?.sslCertIamArn ?? null,
},
},
securityGroup,
vpc
Expand Down
6 changes: 3 additions & 3 deletions lib/chat/api/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { IAuthorizer, RestApi } from 'aws-cdk-lib/aws-apigateway';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import { ISecurityGroup } from 'aws-cdk-lib/aws-ec2';
import { Role } from 'aws-cdk-lib/aws-iam';
import { LayerVersion } from 'aws-cdk-lib/aws-lambda';
import { LayerVersion, Runtime } from 'aws-cdk-lib/aws-lambda';
import { StringParameter } from 'aws-cdk-lib/aws-ssm';
import { Construct } from 'constructs';

Expand Down Expand Up @@ -153,10 +153,10 @@ export class SessionApi extends Construct {
this,
restApi,
authorizer,
config.lambdaSourcePath,
'./lambda',
[commonLambdaLayer],
f,
config.lambdaConfig.pythonRuntime,
Runtime.PYTHON_3_10,
lambdaRole,
vpc,
securityGroups,
Expand Down
6 changes: 3 additions & 3 deletions lib/core/layers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { BundlingOutput } from 'aws-cdk-lib';
import { Architecture, Code, LayerVersion } from 'aws-cdk-lib/aws-lambda';
import { Architecture, Code, LayerVersion, Runtime } from 'aws-cdk-lib/aws-lambda';
import { Asset } from 'aws-cdk-lib/aws-s3-assets';
import { Construct } from 'constructs';

Expand Down Expand Up @@ -84,7 +84,7 @@ export class Layer extends Construct {
const layerAsset = new Asset(this, 'LayerAsset', {
path,
bundling: {
image: config.lambdaConfig.pythonRuntime.bundlingImage,
image: Runtime.PYTHON_3_10.bundlingImage,
platform: architecture.dockerPlatform,
command: ['bash', '-c', `set -e ${args.join(' ')}`],
outputType: BundlingOutput.AUTO_DISCOVER,
Expand All @@ -97,7 +97,7 @@ export class Layer extends Construct {

const layer = new LayerVersion(this, 'Layer', {
code: layerCode,
compatibleRuntimes: [config.lambdaConfig.pythonRuntime],
compatibleRuntimes: [Runtime.PYTHON_3_10],
removalPolicy: config.removalPolicy,
description: description,
});
Expand Down
4 changes: 2 additions & 2 deletions lib/models/docker-image-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { Construct } from 'constructs';
import { Code, Function } from 'aws-cdk-lib/aws-lambda';
import { Code, Function, Runtime } from 'aws-cdk-lib/aws-lambda';
import { Role, InstanceProfile, ServicePrincipal, ManagedPolicy, Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { Stack, Duration } from 'aws-cdk-lib';
import { Bucket } from 'aws-cdk-lib/aws-s3';
Expand Down Expand Up @@ -123,7 +123,7 @@ export class DockerImageBuilder extends Construct {
enforceSSL: true,
}),
functionName: functionId,
runtime: props.config.lambdaConfig.pythonRuntime,
runtime: Runtime.PYTHON_3_10,
handler: 'dockerimagebuilder.handler',
code: Code.fromAsset('./lambda/'),
timeout: Duration.minutes(1),
Expand Down
Loading
Loading