diff --git a/.gitignore b/.gitignore index 8278060..cc9bc24 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .venv files -cdk .DS_STORE -assets/video.mov \ No newline at end of file +assets/video.mov +__pycache__ +cdk/cdk.out \ No newline at end of file diff --git a/cdk/README.md b/cdk/README.md new file mode 100644 index 0000000..c53f0b5 --- /dev/null +++ b/cdk/README.md @@ -0,0 +1,58 @@ + +# Welcome to your CDK Python project! + +This is a blank project for CDK development with Python. + +The `cdk.json` file tells the CDK Toolkit how to execute your app. + +This project is set up like a standard Python project. The initialization +process also creates a virtualenv within this project, stored under the `.venv` +directory. To create the virtualenv it assumes that there is a `python3` +(or `python` for Windows) executable in your path with access to the `venv` +package. If for any reason the automatic creation of the virtualenv fails, +you can create the virtualenv manually. + +To manually create a virtualenv on MacOS and Linux: + +``` +$ python3 -m venv .venv +``` + +After the init process completes and the virtualenv is created, you can use the following +step to activate your virtualenv. + +``` +$ source .venv/bin/activate +``` + +If you are a Windows platform, you would activate the virtualenv like this: + +``` +% .venv\Scripts\activate.bat +``` + +Once the virtualenv is activated, you can install the required dependencies. + +``` +$ pip install -r requirements.txt +``` + +At this point you can now synthesize the CloudFormation template for this code. + +``` +$ cdk synth +``` + +To add additional dependencies, for example other CDK libraries, just add +them to your `setup.py` file and rerun the `pip install -r requirements.txt` +command. + +## Useful commands + + * `cdk ls` list all stacks in the app + * `cdk synth` emits the synthesized CloudFormation template + * `cdk deploy` deploy this stack to your default AWS account/region + * `cdk diff` compare deployed stack with current state + * `cdk docs` open CDK documentation + +Enjoy! diff --git a/cdk/app.py b/cdk/app.py new file mode 100644 index 0000000..32f971f --- /dev/null +++ b/cdk/app.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +import os + +import aws_cdk as cdk + +from cdk.cdk_stack import CdkStack + + +app = cdk.App() +CdkStack(app, "CdkStack", + # 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=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')), + + # Uncomment the next line if you know exactly what Account and Region you + # want to deploy the stack to. */ + + #env=cdk.Environment(account='123456789012', region='us-east-1'), + + # For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html + ) + +app.synth() diff --git a/cdk/cdk.json b/cdk/cdk.json new file mode 100644 index 0000000..d270101 --- /dev/null +++ b/cdk/cdk.json @@ -0,0 +1,49 @@ +{ + "app": "python3 app.py", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "requirements*.txt", + "source.bat", + "**/__init__.py", + "python/__pycache__", + "tests" + ] + }, + "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 + } +} diff --git a/cdk/cdk/__init__.py b/cdk/cdk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cdk/cdk/cdk_stack.py b/cdk/cdk/cdk_stack.py new file mode 100644 index 0000000..8dc9848 --- /dev/null +++ b/cdk/cdk/cdk_stack.py @@ -0,0 +1,107 @@ +from aws_cdk import ( + Duration, + Stack, + aws_lambda as lambda_, + aws_s3 as s3, + aws_sagemaker as sagemaker, + aws_s3_deployment as s3deploy, + aws_iam as iam, + RemovalPolicy, +) + +from .constants import SAGEMAKER_ENDPOINT_NAME, INFERENCE_IMAGE +from constructs import Construct + +class CdkStack(Stack): + + def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: + super().__init__(scope, construct_id, **kwargs) + + # create bucket which will store machine learning model tar gz + bucket = s3.Bucket( + self, "CdkBucket", + versioned=True, + removal_policy=RemovalPolicy.DESTROY, + auto_delete_objects=True, + ) + + # upload file to bucket + s3deploy.BucketDeployment( + self, + 'MlModel', + sources=[s3deploy.Source.asset('../ml/results/zip')], + destination_bucket=bucket + ) + + sagemaker_role = iam.Role(self, 'sagemaker-role', + assumed_by=iam.ServicePrincipal('sagemaker.amazonaws.com')) + sagemaker_role.add_managed_policy(iam.ManagedPolicy.from_aws_managed_policy_name('AmazonSageMakerFullAccess')) + sagemaker_role.add_to_policy(iam.PolicyStatement( + resources=['arn:aws:s3:::*'], + actions=['s3:GetObject', + 's3:PutObject', + 's3:DeleteObject', + 's3:ListBucket'])) + + # create model + model = sagemaker.CfnModel( + self, + "MLInferenceModel", + model_name="my-model", + execution_role_arn=sagemaker_role.role_arn, + containers=[{ + "image": INFERENCE_IMAGE, + "mode": "SingleModel", + "modelDataUrl": f"s3://{bucket.bucket_name}/model.tar.gz" + }] + ) + + # create endpoint configuration + endpoint_config = sagemaker.CfnEndpointConfig( + self, + "EndpointConfig", + endpoint_config_name="my-endpoint-config", + production_variants=[ + sagemaker.CfnEndpointConfig.ProductionVariantProperty( + initial_variant_weight=1, + model_name=model.model_name, + variant_name="single-variant", + serverless_config=sagemaker.CfnEndpointConfig.ServerlessConfigProperty( + max_concurrency=8, + memory_size_in_mb=3072, + ), + ), + ], + ) + + # create endpoint + endpoint = sagemaker.CfnEndpoint( + self, + "Endpoint", + endpoint_name=SAGEMAKER_ENDPOINT_NAME, + endpoint_config_name=endpoint_config.endpoint_config_name, + ) + + + # create lambda from code that is in the lambda folder and add it to the stack + lambda_code = lambda_.Code.from_asset("../lambda") + ep_lambda = lambda_.Function( + self, "MnistHandlerLambda", + code=lambda_code, + handler="index.lambda_handler", + runtime=lambda_.Runtime.PYTHON_3_7, + timeout=Duration.seconds(300), + environment={ + SAGEMAKER_ENDPOINT_NAME: endpoint.endpoint_name, + } + ) + + # # add permission to lambda to invoke sagemaker endpoint + ep_lambda.add_to_role_policy( + iam.PolicyStatement( + actions=["sagemaker:InvokeEndpoint"], + resources=[endpoint.attr_endpoint_name], + ) + ) + + diff --git a/cdk/cdk/constants.py b/cdk/cdk/constants.py new file mode 100644 index 0000000..9b350ae --- /dev/null +++ b/cdk/cdk/constants.py @@ -0,0 +1,2 @@ +SAGEMAKER_ENDPOINT_NAME = 'sagemaker-endpoint-mnist' +INFERENCE_IMAGE = '763104351884.dkr.ecr.eu-central-1.amazonaws.com/pytorch-inference:1.10.2-cpu-py38-ubuntu20.04-sagemaker' \ No newline at end of file diff --git a/cdk/requirements-dev.txt b/cdk/requirements-dev.txt new file mode 100644 index 0000000..9270945 --- /dev/null +++ b/cdk/requirements-dev.txt @@ -0,0 +1 @@ +pytest==6.2.5 diff --git a/cdk/requirements.txt b/cdk/requirements.txt new file mode 100644 index 0000000..d3bb15a --- /dev/null +++ b/cdk/requirements.txt @@ -0,0 +1,2 @@ +aws-cdk-lib==2.72.0 +constructs>=10.0.0,<11.0.0