From 6c0fac5f49bd8928a2faee1425fcd0f6dfe9876f Mon Sep 17 00:00:00 2001 From: drduhe Date: Wed, 1 Nov 2023 12:38:57 -0600 Subject: [PATCH] feat: breaking out ECR repos into their own Constructs" --- lib/index.ts | 4 +- lib/osml/model_runner/mr_dataplane.ts | 49 +--------- lib/osml/model_runner/mr_ecr.ts | 95 ++++++++++++++++++ ...{mr_model_endpoints.ts => mr_endpoints.ts} | 70 ++----------- lib/osml/model_runner/testing/mr_model_ecr.ts | 97 +++++++++++++++++++ 5 files changed, 206 insertions(+), 109 deletions(-) create mode 100644 lib/osml/model_runner/mr_ecr.ts rename lib/osml/model_runner/testing/{mr_model_endpoints.ts => mr_endpoints.ts} (79%) create mode 100644 lib/osml/model_runner/testing/mr_model_ecr.ts diff --git a/lib/index.ts b/lib/index.ts index 8192a95..cc4f1fb 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,12 +1,14 @@ export * from "./osml/model_runner/autoscaling/mr_autoscaling"; export * from "./osml/model_runner/mr_dataplane"; +export * from "./osml/model_runner/mr_ecr"; export * from "./osml/model_runner/monitoring/mr_monitoring"; export * from "./osml/model_runner/roles/mr_sm_role"; export * from "./osml/model_runner/roles/mr_task_role"; export * from "./osml/model_runner/testing/mr_imagery"; -export * from "./osml/model_runner/testing/mr_model_endpoints"; +export * from "./osml/model_runner/testing/mr_endpoints"; export * from "./osml/model_runner/testing/mr_status"; export * from "./osml/model_runner/testing/mr_sync"; +export * from "./osml/model_runner/testing/mr_model_ecr"; export * from "./osml/osml_account"; export * from "./osml/osml_bucket"; export * from "./osml/osml_ecr_deployment"; diff --git a/lib/osml/model_runner/mr_dataplane.ts b/lib/osml/model_runner/mr_dataplane.ts index 89a0800..25b9a7a 100644 --- a/lib/osml/model_runner/mr_dataplane.ts +++ b/lib/osml/model_runner/mr_dataplane.ts @@ -2,15 +2,9 @@ * Copyright 2023 Amazon.com, Inc. or its affiliates. */ -import { - Duration, - region_info, - RemovalPolicy, - SymlinkFollowMode -} from "aws-cdk-lib"; +import { Duration, region_info, RemovalPolicy } from "aws-cdk-lib"; import { AttributeType } from "aws-cdk-lib/aws-dynamodb"; import { ISecurityGroup, SecurityGroup } from "aws-cdk-lib/aws-ec2"; -import { DockerImageAsset } from "aws-cdk-lib/aws-ecr-assets"; import { Cluster, Compatibility, @@ -29,7 +23,6 @@ import { LogGroup, RetentionDays } from "aws-cdk-lib/aws-logs"; import { Construct } from "constructs"; import { OSMLAccount } from "../osml_account"; -import { OSMLECRDeployment } from "../osml_ecr_deployment"; import { OSMLQueue } from "../osml_queue"; import { OSMLTable } from "../osml_table"; import { OSMLTopic } from "../osml_topic"; @@ -68,14 +61,7 @@ export class MRDataplaneConfig { public MR_LOGGING_MEMORY = 512, public MR_LOGGING_CPU = 512, public MR_WORKERS_PER_CPU = 1, - public MR_REGION_SIZE = "(8192, 8192)", - public MR_DEFAULT_CONTAINER = "awsosml/osml-model-runner:main", - // repository name for the model runner container - public ECR_MODEL_RUNNER_REPOSITORY = "model-runner-container", - // path to the local source for model runner to build against - public ECR_MODEL_RUNNER_BUILD_PATH = "lib/osml-model-runner", - // build target for model runner container - public ECR_MODEL_RUNNER_TARGET = "model_runner" + public MR_REGION_SIZE = "(8192, 8192)" ) {} } @@ -94,6 +80,7 @@ export interface MRDataplaneProps { dataplaneConfig?: MRDataplaneConfig; // subnets to deploy infrastructure into targetSubnets?: string[]; + mrContainerImage: ContainerImage; } export class MRDataplane extends Construct { @@ -105,8 +92,6 @@ export class MRDataplane extends Construct { public featureTable: OSMLTable; public endpointStatisticsTable: OSMLTable; public regionRequestTable: OSMLTable; - public mrContainerSourceUri: string; - public mrContainerImage: ContainerImage; public imageStatusTopic: OSMLTopic; public regionStatusTopic: OSMLTopic; public imageRequestQueue: OSMLQueue; @@ -126,7 +111,6 @@ export class MRDataplane extends Construct { * - creating the DDB tables * - creating the SQS queues * - creating the SNS topics - * - creating the ECR repositories * - creating the ECR containers * - creating the ECS cluster * - creating the ECS task definition @@ -172,31 +156,6 @@ export class MRDataplane extends Construct { region_info.FactName.servicePrincipal("s3.amazonaws.com") )!; - if (props.account.isDev == true) { - const dockerImageAsset = new DockerImageAsset(this, id, { - directory: this.mrDataplaneConfig.ECR_MODEL_RUNNER_BUILD_PATH, - file: "Dockerfile", - followSymlinks: SymlinkFollowMode.ALWAYS, - target: this.mrDataplaneConfig.ECR_MODEL_RUNNER_TARGET - }); - this.mrContainerImage = - ContainerImage.fromDockerImageAsset(dockerImageAsset); - this.mrContainerSourceUri = dockerImageAsset.imageUri; - } else { - const osmlEcrDeployment = new OSMLECRDeployment( - this, - "MRModelRunnerContainer", - { - sourceUri: this.mrDataplaneConfig.MR_DEFAULT_CONTAINER, - repositoryName: this.mrDataplaneConfig.ECR_MODEL_RUNNER_REPOSITORY, - removalPolicy: this.removalPolicy, - vpc: props.osmlVpc.vpc - } - ); - this.mrContainerImage = osmlEcrDeployment.containerImage; - this.mrContainerSourceUri = osmlEcrDeployment.ecrContainerUri; - } - // job status table to store worker status info this.jobStatusTable = new OSMLTable(this, "MRJobStatusTable", { tableName: this.mrDataplaneConfig.DDB_JOB_STATUS_TABLE, @@ -344,7 +303,7 @@ export class MRDataplane extends Construct { "MRContainerDefinition", { containerName: this.mrDataplaneConfig.MR_CONTAINER_NAME, - image: this.mrContainerImage, + image: props.mrContainerImage, memoryLimitMiB: this.mrDataplaneConfig.MR_CONTAINER_MEMORY, cpu: this.mrDataplaneConfig.MR_CONTAINER_CPU, environment: containerEnv, diff --git a/lib/osml/model_runner/mr_ecr.ts b/lib/osml/model_runner/mr_ecr.ts new file mode 100644 index 0000000..b6f7842 --- /dev/null +++ b/lib/osml/model_runner/mr_ecr.ts @@ -0,0 +1,95 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + */ + +import { RemovalPolicy, SymlinkFollowMode } from "aws-cdk-lib"; +import { DockerImageAsset } from "aws-cdk-lib/aws-ecr-assets"; +import { ContainerImage } from "aws-cdk-lib/aws-ecs"; +import { IRole } from "aws-cdk-lib/aws-iam"; +import { Construct } from "constructs"; + +import { OSMLAccount } from "../osml_account"; +import { OSMLECRDeployment } from "../osml_ecr_deployment"; +import { OSMLVpc } from "../osml_vpc"; + +// mutable configuration dataclass for model runner +// for a more detailed breakdown of the configuration see: configuration_guide.md in the documentation directory. +export class MRRepoConfig { + constructor( + public MR_DEFAULT_CONTAINER = "awsosml/osml-model-runner:main", + // repository name for the model runner container + public ECR_MODEL_RUNNER_REPOSITORY = "model-runner-container", + // path to the local source for model runner to build against + public ECR_MODEL_RUNNER_BUILD_PATH = "lib/osml-model-runner", + // build target for model runner container + public ECR_MODEL_RUNNER_TARGET = "model_runner" + ) {} +} + +export interface MRRepoProps { + // the account that owns the data plane as defined by the OSMLAccount interface + account: OSMLAccount; + // the vpc to deploy into + osmlVpc: OSMLVpc; + // optional role to give the model runner task execution permissions - will be crated if not provided + taskRole?: IRole; + // optional service level configuration that can be provided by the user but will be defaulted if not + mrRepoConfig?: MRRepoConfig; +} + +export class MREcr extends Construct { + public mrRepoConfig: MRRepoConfig; + public removalPolicy: RemovalPolicy; + public mrContainerSourceUri: string; + public mrContainerImage: ContainerImage; + + /** + * This construct is responsible deploying the ECR container image for + * the model runner service. + * @param scope the scope/stack in which to define this construct. + * @param id the id of this construct within the current scope. + * @param props the properties of this construct. + * @returns the MRDataplane construct + */ + constructor(scope: Construct, id: string, props: MRRepoProps) { + super(scope, id); + // setup a removal policy + this.removalPolicy = props.account.prodLike + ? RemovalPolicy.RETAIN + : RemovalPolicy.DESTROY; + + // check if a custom configuration was provided + if (props.mrRepoConfig != undefined) { + // import existing pass in MR configuration + this.mrRepoConfig = props.mrRepoConfig; + } else { + // create a new default configuration + this.mrRepoConfig = new MRRepoConfig(); + } + + if (props.account.isDev == true) { + const dockerImageAsset = new DockerImageAsset(this, id, { + directory: this.mrRepoConfig.ECR_MODEL_RUNNER_BUILD_PATH, + file: "Dockerfile", + followSymlinks: SymlinkFollowMode.ALWAYS, + target: this.mrRepoConfig.ECR_MODEL_RUNNER_TARGET + }); + this.mrContainerImage = + ContainerImage.fromDockerImageAsset(dockerImageAsset); + this.mrContainerSourceUri = dockerImageAsset.imageUri; + } else { + const osmlEcrDeployment = new OSMLECRDeployment( + this, + "MRModelRunnerContainer", + { + sourceUri: this.mrRepoConfig.MR_DEFAULT_CONTAINER, + repositoryName: this.mrRepoConfig.ECR_MODEL_RUNNER_REPOSITORY, + removalPolicy: this.removalPolicy, + vpc: props.osmlVpc.vpc + } + ); + this.mrContainerImage = osmlEcrDeployment.containerImage; + this.mrContainerSourceUri = osmlEcrDeployment.ecrContainerUri; + } + } +} diff --git a/lib/osml/model_runner/testing/mr_model_endpoints.ts b/lib/osml/model_runner/testing/mr_endpoints.ts similarity index 79% rename from lib/osml/model_runner/testing/mr_model_endpoints.ts rename to lib/osml/model_runner/testing/mr_endpoints.ts index 70c3d0a..4f04032 100644 --- a/lib/osml/model_runner/testing/mr_model_endpoints.ts +++ b/lib/osml/model_runner/testing/mr_endpoints.ts @@ -1,8 +1,7 @@ /* * Copyright 2023 Amazon.com, Inc. or its affiliates. */ -import { RemovalPolicy, SymlinkFollowMode } from "aws-cdk-lib"; -import { DockerImageAsset } from "aws-cdk-lib/aws-ecr-assets"; +import { RemovalPolicy } from "aws-cdk-lib"; import { ContainerImage } from "aws-cdk-lib/aws-ecs"; import { IRole } from "aws-cdk-lib/aws-iam"; import { Construct } from "constructs"; @@ -11,7 +10,6 @@ import { OSMLHTTPModelEndpoint } from "../../model_endpoints/osml_http_endpoint" import { OSMLHTTPEndpointRole } from "../../model_endpoints/osml_http_endpoint_role"; import { OSMLSMEndpoint } from "../../model_endpoints/osml_sm_endpoint"; import { OSMLAccount } from "../../osml_account"; -import { OSMLECRDeployment } from "../../osml_ecr_deployment"; import { OSMLVpc } from "../../osml_vpc"; import { MRSMRole } from "../roles/mr_sm_role"; @@ -38,7 +36,6 @@ export class MRModelEndpointsConfig { public HTTP_ENDPOINT_CPU = 4096, public HTTP_ENDPOINT_HEALTHCHECK_PATH = "/ping", public HTTP_ENDPOINT_DOMAIN_NAME = "test-http-model-endpoint", - public MODEL_DEFAULT_CONTAINER = "awsosml/osml-models:main", // ecr repo names public ECR_MODEL_REPOSITORY = "model-container", // path to the control model source @@ -60,8 +57,6 @@ export interface MRModelEndpointsProps { mrModelEndpointsConfig?: MRModelEndpointsConfig; // optional sage maker iam role to use for endpoint construction - will be defaulted if not provided smRole?: IRole; - // optional custom model container ECR URIs - modelContainer?: string; // security groups to apply to the vpc config for SM endpoints securityGroupId?: string; // optional deploy custom model resources @@ -69,10 +64,11 @@ export interface MRModelEndpointsProps { deployFloodModel?: boolean; deployAircraftModel?: boolean; deployHttpCenterpointModel?: boolean; + modelContainerImage: ContainerImage; + modelContainerUri: string; } export class MREndpoints extends Construct { - public modelContainerEcrDeployment: OSMLECRDeployment; public removalPolicy: RemovalPolicy; public mrModelEndpointsConfig: MRModelEndpointsConfig; public smRole?: IRole; @@ -82,8 +78,6 @@ export class MREndpoints extends Construct { public floodModelEndpoint?: OSMLSMEndpoint; public aircraftModelEndpoint?: OSMLSMEndpoint; public securityGroupId: string; - public modelContainerImage: ContainerImage; - public modelContainerUri: string; /** * Creates an MRTesting construct. @@ -133,36 +127,6 @@ export class MREndpoints extends Construct { } else { this.securityGroupId = props.osmlVpc.vpcDefaultSecurityGroup; } - - if (props.account.isDev == true) { - const dockerImageAsset = new DockerImageAsset(this, id, { - directory: this.mrModelEndpointsConfig.ECR_MODELS_PATH, - file: "Dockerfile", - followSymlinks: SymlinkFollowMode.ALWAYS, - target: this.mrModelEndpointsConfig.ECR_MODEL_TARGET - }); - - this.modelContainerImage = - ContainerImage.fromDockerImageAsset(dockerImageAsset); - - this.modelContainerUri = dockerImageAsset.imageUri; - } else { - this.modelContainerEcrDeployment = new OSMLECRDeployment( - this, - "OSMLModelContainer", - { - sourceUri: this.mrModelEndpointsConfig.MODEL_DEFAULT_CONTAINER, - repositoryName: this.mrModelEndpointsConfig.ECR_MODEL_REPOSITORY, - removalPolicy: this.removalPolicy, - vpc: props.osmlVpc.vpc, - vpcSubnets: props.osmlVpc.selectedSubnets - } - ); - this.modelContainerImage = - this.modelContainerEcrDeployment.containerImage; - this.modelContainerUri = - this.modelContainerEcrDeployment.ecrContainerUri; - } } if (props.deployHttpCenterpointModel != false) { // check if a role was provided @@ -188,7 +152,7 @@ export class MREndpoints extends Construct { { account: props.account, osmlVpc: props.osmlVpc, - image: this.modelContainerImage, + image: props.modelContainerImage, clusterName: this.mrModelEndpointsConfig.HTTP_ENDPOINT_NAME, role: this.httpEndpointRole, memory: this.mrModelEndpointsConfig.HTTP_ENDPOINT_MEMORY, @@ -206,11 +170,6 @@ export class MREndpoints extends Construct { securityGroupId: this.securityGroupId } ); - if (props.account.isDev == false) { - this.httpCenterpointModelEndpoint.node.addDependency( - this.modelContainerEcrDeployment - ); - } } if (props.deployCenterpointModel != false) { @@ -219,7 +178,7 @@ export class MREndpoints extends Construct { this, "OSMLCenterPointModelEndpoint", { - ecrContainerUri: this.modelContainerUri, + ecrContainerUri: props.modelContainerUri, modelName: this.mrModelEndpointsConfig.SM_CENTER_POINT_MODEL, roleArn: this.smRole.roleArn, instanceType: this.mrModelEndpointsConfig.SM_CPU_INSTANCE_TYPE, @@ -234,11 +193,6 @@ export class MREndpoints extends Construct { subnetIds: props.osmlVpc.selectedSubnets.subnetIds } ); - if (props.account.isDev == false) { - this.centerPointModelEndpoint.node.addDependency( - this.modelContainerEcrDeployment - ); - } } if (props.deployFloodModel != false) { @@ -247,7 +201,7 @@ export class MREndpoints extends Construct { this, "OSMLFloodModelEndpoint", { - ecrContainerUri: this.modelContainerUri, + ecrContainerUri: props.modelContainerUri, modelName: this.mrModelEndpointsConfig.SM_FLOOD_MODEL, roleArn: this.smRole.roleArn, instanceType: this.mrModelEndpointsConfig.SM_CPU_INSTANCE_TYPE, @@ -262,11 +216,6 @@ export class MREndpoints extends Construct { subnetIds: props.osmlVpc.selectedSubnets.subnetIds } ); - if (props.account.isDev == false) { - this.floodModelEndpoint.node.addDependency( - this.modelContainerEcrDeployment - ); - } } if (props.deployAircraftModel != false) { @@ -275,7 +224,7 @@ export class MREndpoints extends Construct { this, "OSMLAircraftModelEndpoint", { - ecrContainerUri: this.modelContainerUri, + ecrContainerUri: props.modelContainerUri, modelName: this.mrModelEndpointsConfig.SM_AIRCRAFT_MODEL, roleArn: this.smRole.roleArn, instanceType: this.mrModelEndpointsConfig.SM_GPU_INSTANCE_TYPE, @@ -290,11 +239,6 @@ export class MREndpoints extends Construct { subnetIds: props.osmlVpc.selectedSubnets.subnetIds } ); - if (props.account.isDev == false) { - this.aircraftModelEndpoint.node.addDependency( - this.modelContainerEcrDeployment - ); - } } } } diff --git a/lib/osml/model_runner/testing/mr_model_ecr.ts b/lib/osml/model_runner/testing/mr_model_ecr.ts new file mode 100644 index 0000000..bc334d3 --- /dev/null +++ b/lib/osml/model_runner/testing/mr_model_ecr.ts @@ -0,0 +1,97 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + */ +import { RemovalPolicy, SymlinkFollowMode } from "aws-cdk-lib"; +import { DockerImageAsset } from "aws-cdk-lib/aws-ecr-assets"; +import { ContainerImage } from "aws-cdk-lib/aws-ecs"; +import { Construct } from "constructs"; + +import { OSMLAccount } from "../../osml_account"; +import { OSMLECRDeployment } from "../../osml_ecr_deployment"; +import { OSMLVpc } from "../../osml_vpc"; + +// mutable configuration dataclass for the model runner testing Construct +// for a more detailed breakdown of the configuration see: configuration_guide.md in the documentation directory. +export class MRModelRepoConfig { + constructor( + public MODEL_DEFAULT_CONTAINER = "awsosml/osml-models:main", + // ecr repo names + public ECR_MODEL_REPOSITORY = "model-container", + // path to the control model source + public ECR_MODELS_PATH = "lib/osml-models", + // build target for control model container + public ECR_MODEL_TARGET = "osml_model" + ) {} +} + +export interface MRModelRepoProps { + // the osml account interface + account: OSMLAccount; + // the model runner vpc + osmlVpc: OSMLVpc; + // optional custom configuration for the testing resources - will be defaulted if not provided + mrModelRepoConfig?: MRModelRepoConfig; +} + +export class MRModelEcr extends Construct { + public modelContainerEcrDeployment: OSMLECRDeployment; + public mrModelRepoConfig: MRModelRepoConfig; + public modelContainerImage: ContainerImage; + public modelContainerUri: string; + public removalPolicy: RemovalPolicy; + + /** + * This construct is responsible deploying the ECR container image for + * the model to be used with model runner. + * @param scope the scope/stack in which to define this construct. + * @param id the id of this construct within the current scope. + * @param props the properties of this construct. + * @returns MRModelRepo Construct + */ + constructor(scope: Construct, id: string, props: MRModelRepoProps) { + super(scope, id); + + // setup a removal policy + this.removalPolicy = props.account.prodLike + ? RemovalPolicy.RETAIN + : RemovalPolicy.DESTROY; + + // check if a custom config was provided + if (props.mrModelRepoConfig != undefined) { + // import existing pass in MR configuration + this.mrModelRepoConfig = props.mrModelRepoConfig; + } else { + // create a new default configuration + this.mrModelRepoConfig = new MRModelRepoConfig(); + } + + if (props.account.isDev == true) { + const dockerImageAsset = new DockerImageAsset(this, id, { + directory: this.mrModelRepoConfig.ECR_MODELS_PATH, + file: "Dockerfile", + followSymlinks: SymlinkFollowMode.ALWAYS, + target: this.mrModelRepoConfig.ECR_MODEL_TARGET + }); + + this.modelContainerImage = + ContainerImage.fromDockerImageAsset(dockerImageAsset); + + this.modelContainerUri = dockerImageAsset.imageUri; + } else { + this.modelContainerEcrDeployment = new OSMLECRDeployment( + this, + "OSMLModelContainer", + { + sourceUri: this.mrModelRepoConfig.MODEL_DEFAULT_CONTAINER, + repositoryName: this.mrModelRepoConfig.ECR_MODEL_REPOSITORY, + removalPolicy: this.removalPolicy, + vpc: props.osmlVpc.vpc, + vpcSubnets: props.osmlVpc.selectedSubnets + } + ); + this.modelContainerImage = + this.modelContainerEcrDeployment.containerImage; + this.modelContainerUri = this.modelContainerEcrDeployment.ecrContainerUri; + } + } +}