avatarRamon Marrero

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

9438

Abstract

ort core

from dataproduct<span class="hljs-selector-class">.dataproduct_stack</span> import DataproductStack

app = core<span class="hljs-selector-class">.App</span>()

<span class="hljs-function"><span class="hljs-title">DataproductStack</span><span class="hljs-params">(app, <span class="hljs-string">"dataproduct"</span>)</span></span>

app<span class="hljs-selector-class">.synth</span>()</pre></div><p id="5916">To successfully create some of the components needed in our stack, it is necessary to include an <b>environment</b>. As stated by <a href="https://docs.aws.amazon.com/cdk/latest/guide/environments.html">AWS</a>, an environment is the target AWS account and region into which the stack is intended to be deployed.</p><p id="d3d1">Each stack is explicitly or implicitly associated with an AWS account and region.</p><div id="d411"><pre>env = core.Environment(<span class="hljs-attribute">account</span>=<span class="hljs-string">"yourawsaccount"</span>, <span class="hljs-attribute">region</span>=<span class="hljs-string">"yourawsregion"</span>)</pre></div><p id="2159">Once created, the environment can be provided to the stack.</p><div id="1669"><pre><span class="hljs-function"><span class="hljs-title">DataproductStack</span><span class="hljs-params">(app, <span class="hljs-string">"dataproduct"</span>, env=env)</span></span></pre></div><p id="6f96">After the recent changes, the content of <b>dataproduct/app.py</b> file should like the code below:</p><blockquote id="92c8"><p><b>Important: </b>Provide your own AWS account and region.</p></blockquote><div id="fb79"><pre><span class="hljs-keyword">from</span> aws_cdk <span class="hljs-keyword">import</span> core

<span class="hljs-keyword">from</span> dataproduct.dataproduct_stack <span class="hljs-keyword">import</span> DataproductStack

app = core.App()</pre></div><div id="c013"><pre>env = core.Environment(<span class="hljs-attribute">account</span>=<span class="hljs-string">"yourawsaccount"</span>, <span class="hljs-attribute">region</span>=<span class="hljs-string">"yourawsregion"</span>)

DataproductStack(app, <span class="hljs-string">"dataproduct"</span>, <span class="hljs-attribute">env</span>=env)

app.synth()</pre></div><h1 id="ecc8">Creating additional directories</h1><p id="ad97">We will need 2 additional directories to be created under the dataproduct folder: <b>function and product.</b></p><p id="62f6">· The <b>function </b>directory will contain the Lambda code.</p><p id="f963">· The <b>product </b>directory will contain our Dockerfile for the data product.</p><p id="ea32">Let’s create both of them <i>under the main dataproduct</i> directory.</p><div id="3e69"><pre>mkdir <span class="hljs-keyword">function</span> <span class="hljs-title">product</span></pre></div><h2 id="9182">Lambda Function Code</h2><p id="5cff">Create a python file <b>dataproduct-lambda.py</b> under the <b>function </b>directory and include the code below.</p> <figure id="1e2a"> <div> <div>

            <iframe class="gist-iframe" src="/gist/ramonmarrero/d95ac771aaafb8da56a35938de011392.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
          </div>
        </div>
    </figure></iframe></div></div></figure><p id="f328">This code will enable the lambda function to run a task using the ECS Cluster and Task Definition provided.</p><h2 id="191d">Product</h2><p id="0378">As for the product, create a <b>Dockerfile </b>under the <b>product </b>directory and include the line below:</p><div id="9117"><pre><span class="hljs-attribute">FROM</span> python:<span class="hljs-number">3</span>.<span class="hljs-number">8</span>-slim-buster</pre></div><p id="30d7">This will build a docker container based on python 3.8.</p><h1 id="87a4">Creating the stack</h1><p id="0c41">We are now ready to add constructs to our stack using the default stack already created for us. It can be found under the main directory in the path:</p><p id="8758"><code>dataproduct/dataproduct_stack.py</code></p><p id="06c0">At the moment, it is an empty stack. For the stack to be useful, it will need <b>constructs </b>to be defined within it.</p><p id="bfe7">There are different levels of constructs: L1, L2 (low-level constructs, and high-level constructs respectively), and patterns. You can find additional information regarding constructs in <a href="https://docs.aws.amazon.com/cdk/latest/guide/constructs.html">https://docs.aws.amazon.com/cdk/latest/guide/constructs.html</a>.</p><p id="0a62">In this article, we will be using L1 and L2 constructs. The code below can be found in the git repo <a href="https://github.com/ramonmarrero/demo-dataproduct-cdk">https://github.com/ramonmarrero/demo-dataproduct-cdk</a>.</p><blockquote id="c078"><p><b>Important:</b> Replace the vpc and subnet values for your own.</p></blockquote>
    <figure id="2324">
        <div>
          <div>
            
            <iframe class="gist-iframe" src="/gist/ramonmarrero/07d274e4debaaefb905b739b83c4ea19.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
          </div>
        </div>
    </figure></iframe></div></div></figure><p id="015d">This might look overwhelming at first, specially if it is the first stack you are creating. To clarify the process, I will proceed to explain each resource in the stack in separate sections.</p><h2 id="e633">VPC</h2><p id="f35c">Let’s start with the first construct we can see in action, the <a href="https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_ec2/Vpc.html"><code></code>VPC</a> construct. In this case, it is used to retrieve an existing VPC:</p><div id="84c6"><pre>ec2<span class="hljs-selector-class">.Vpc</span><span class="hljs-selector-class">.from_lookup</span>(scope, id, vpc_id)</pre></div><h2 id="a439">ECS Cluster</h2><p id="0418">The <a href="https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ecs.Cluster.html"><code>ECS Clus</code>ter</a>construct is part of the ECS module, which contains constructs for working with Amazon Elastic Container Service (Amazon ECS). In this particular case, we are creating a cluster using the VPC previously selected:</p><div id="7ab6"><pre>ecs.Cluster(scope, <span class="hljs-built_in">id</span>, vpc)</pre></div><h2 id="3d02">Tags</h2><p id="4a6a">In the next part, we create some tags for the cluster. Tags are informational key-value elements that you can add to constructs. The <a href="https://docs.aws.amazon.com/cdk/latest/guide/tagging.html"><b>Tags.of</b></a> is used to provide tag information for the resources created.</p><div id="430a"><pre>Tags.of(my_construct).<span class="hljs-built_in">add</span>(<span class="hljs-string">"key"</span>, <span class="hljs-string">"value"</span>)</pre></div><h2 id="3daa">ECS Task Definition</h2><p id="030f">The next step is to create an <a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definitions.html"><b>ECS task definition</b></a>. A task definition is required to run containers in Amazon ECS. The task definition will need permissions to make AWS API calls such as pulling a container image from ECR and permission for the running task to interact with AWS services.</p><p id="35b8">To enable this, 2 roles should be created: an IAM task execution role and a task role.</p><p id="4b51">We create both IAM roles using the <a href="https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iam.Role.html"><b>Role</b></a> construct:</p><div id="4f1b"><pre>iam.Role(scope, <span class="hljs-built_in">id</span>, assumed_by)</pre></div><blockquote id="1c0b"><p><b>Important:</b> pay close attention to the service principal specified in the code.</p></blockquote><p id="8391">As stated by <a href="https://docs.amazonaws.cn/en_us/aws/latest/userguide/iam.html">AWS</a>, “in IAM policies where the principal is a service, such as Amazon EC2, the service principal name can include the following formats, with “service” replaced by the name of the service”. For instance: <code>service.amazonaws.com</code> .</p><p id="de8d">When creating roles, each service will have its own service principal. It is case sensitive and the format can vary across AWS services. To find the right service principal for your service, AWS recommends to refer to <a href="https://docs.amazonaws.cn/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html">AWS services that work with IAM</a> and look for the services that have <b>Yes </b>in the <b>Service-linked role</b> column.</p><p id="e3ad">Now, it is time to create the ECS task definition using the <a href="https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_ecs/TaskDefinition.html">ECS task definition construct</a> and provide both roles previously created.</p><div id="7328"><pre>ecs.TaskDefinition(scope, <span class="hljs-built_in">id</span>, compatibility, cpu, 

memory_mib, execution_role, task_role)</pre></div><h2 id="85ef">CloudWatch</h2><p id="b999">In order to enable CloudWatch integration with our ECS service, we need to create <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Working-with-log-groups-and-streams.html">Log Groups</a> using the <a href="https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_logs/LogGroup.html">LogGroup construct</a>.</p><div id="b8c3"><pre>logs.LogGroup(scope, <span class="hljs-built_in">id</s

Options

pan>, log_group_name, removal_policy, retention)</pre></div><h2 id="7ca3">ECR</h2><blockquote id="dfb8"><p><b>Important</b>: At the time of writing this article, the <a href="https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_ecr_assets/README.html"><b>Docker Image Assets</b></a> module is experimental and under active development. It is subject to non-backward compatible changes or removal in any future version.</p></blockquote><p id="e455">The next step consists of creating a container repository, building a docker image, and pushing the image to the repository. We can accomplish all these tasks using the Docker Image Assets construct:</p><div id="89f4"><pre>assets.DockerImageAsset(<span class="hljs-keyword">scope, </span>id, <span class="hljs-keyword">directory, </span><span class="hljs-keyword">build_args, </span>file=None, repository_name)</pre></div><h2 id="cc0d">Link Services</h2><p id="0672">Once the task definition, docker image, and logging details have been created, we can link them as below:</p><div id="12e9"><pre>ecs<span class="hljs-selector-class">.TaskDefinition</span><span class="hljs-selector-class">.add_container</span>(id, image, logging)</pre></div><p id="cfcf">Additionally, we can grant pull access for the ECS Task Definition:</p><div id="f4e6"><pre>DockerImageAsset<span class="hljs-selector-class">.repository</span><span class="hljs-selector-class">.grant_pull</span>(grantee)</pre></div><h2 id="6779">Lambda Role</h2><p id="00aa">It is time to create our Lambda resource. Once again, first, we proceed to create the role and policies.</p><div id="4205"><pre>iam.Role(scope, <span class="hljs-built_in">id</span>, assumed_by, managed_policies)</pre></div><p id="087c">This time, we can add managed policies to the role, such as: AWSLambdaVPCAccessExecutionRole and AWSLambdaBasicExecutionRole</p><p id="333c">And add policies to the Lambda Role to be able to access the ECS Execution and Task Role previously created.</p><div id="ce00"><pre>iam.<span class="hljs-keyword">Role</span>.add_to_policy(<span class="hljs-keyword">statement</span>)</pre></div><h2 id="76e4">Lambda Function</h2><p id="9a76">With the lambda role and policies created, we can now create the Lambda function.</p><div id="1233"><pre>lambda.<span class="hljs-keyword">Function</span>(scope, id, runtime, function_name, <span class="hljs-keyword">role</span>, <span class="hljs-keyword">handler</span>, code, environment )</pre></div><h2 id="e5f5">API Gateway</h2><p id="3e64">We will define an API Gateway REST API with AWS Lambda proxy integration as our endpoint.</p><div id="d880"><pre>apigateway.LambdaRestApi(scope, <span class="hljs-built_in">id</span>, handler)</pre></div><p id="7352">The <b>handler </b>reference the lambda function that handles all requests from this API.</p><p id="13a4">And finally, we can add a lambda dependency to be created after ECS Cluster.</p><div id="fd7f"><pre>lambda<span class="hljs-selector-class">.Function</span><span class="hljs-selector-class">.node</span><span class="hljs-selector-class">.add_dependency</span>(cluster)</pre></div><p id="af04">With all the necessary resources already created, it is now time to include CloudFormation stack outputs for informational purposes.</p><div id="4f5e"><pre><span class="hljs-comment"># Lambda</span> core.CfnOutput( self, <span class="hljs-string">"LambdaResource"</span>, <span class="hljs-attribute">description</span>=<span class="hljs-string">"Data Product Lambda Function"</span>, <span class="hljs-attribute">value</span>=lambda_fargate.function_name )

<span class="hljs-comment"># Repository</span> core.CfnOutput( self, <span class="hljs-string">"ECRResourceARN"</span>, <span class="hljs-attribute">description</span>=<span class="hljs-string">"Data Product Image URI"</span>, <span class="hljs-attribute">value</span>=dataProductImage.image_uri )</pre></div><h1 id="6bce">Synthesize your AWS CloudFormation template</h1><p id="8b16">The <code>cdk synth</code> command executes your app, which causes the resources defined in it to be translated to an AWS CloudFormation template.</p><p id="8333">With more than one stack in your app, you will need to specify which stack(s) to synthesize. In our case, there is only one, therefore the CDK can determine which stack to use.</p><p id="ebf6">Let’s synthesize the app by executing the command below:</p><div id="c048"><pre><span class="hljs-attribute">cdk synth</span></pre></div><h1 id="1b0d">Deploy the stack</h1><p id="7f92">The <code>cdk deploy</code> command deploys the stack(s) to your AWS account.</p><div id="5d39"><pre><span class="hljs-attribute">cdk deploy</span></pre></div><p id="0d01">Once executing the command, CDK will show on screen all resources present in the stack and about to be deployed.</p><figure id="ce82"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*o302um6GThskup-f0GZQmA.png"><figcaption>AWS CDK — Output (<i>Image by author</i>)</figcaption></figure><p id="433b">If we proceed to deploy the stack, cloudformation will build our product based on the <b>Docker Image Asset</b> specification. After this first step, all resources will be created, only considering dependencies if they have been specified.</p><p id="ff19">By using the URL endpoint provided in the terminal output, we can trigger the process to access the Lambda function and therefore the ECS Fargate service.</p><figure id="b9e7"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*bOo5-yN7vkETADogZFC8Ow.png"><figcaption>AWS CDK — Output (<i>Image by author</i>)</figcaption></figure><h1 id="3868">Destroy the stack</h1><p id="5955">The <code>cdk destroy</code> command destroy the stack that was previously deployed to your AWS account.</p><div id="7e9f"><pre><span class="hljs-attribute">cdk destroy</span></pre></div><figure id="aada"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*r2xTiDopx7Khyy8QpK2cHw.png"><figcaption>AWS CDK — output (<i>Image by author</i>)</figcaption></figure><h1 id="1215">Conclusion</h1><p id="3055">In this article, we have built and deployed serverless services on AWS using infrastructure as code. We have leverage AWS CDK, an open software development framework, to provide a reliable and reproducible way of creating and deploying our resources. AWS CDK is a powerful tool that enables us to deliver solutions that include both, infrastructure and product, via familiar programming languages such as python.</p><p id="2d2b">Happy coding! Do not forget to follow me or subscribe via email <a href="https://ramon-marrero.medium.com/subscribe">here</a>. You can also support my writing by joining Medium using the following <a href="https://ramon-marrero.medium.com/membership">link</a>. For more AWS content</p><div id="480f" class="link-block"> <a href="https://readmedium.com/deploying-aws-lambda-container-images-43a9e37b25ab"> <div> <div> <h2>Deploying AWS Lambda Container Images</h2> <div><h3>Learn how to package AWS Lambda functions as container images with AWS CDK and Python.</h3></div> <div><p>medium.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/1*OoGKyXHt1875s_B9XWQLjw.png)"></div> </div> </div> </a> </div><div id="3258" class="link-block"> <a href="https://readmedium.com/running-serverless-spark-applications-with-aws-lambda-d7e25795ec1d"> <div> <div> <h2>Running Serverless Spark Applications with AWS Lambda.</h2> <div><h3>Leverage Amazon SageMaker Processing to run serverless Spark applications from AWS Lambda.</h3></div> <div><p>medium.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/1*1YjB_gt-2QvwMCzpJjrFWg.png)"></div> </div> </div> </a> </div><div id="6007" class="link-block"> <a href="https://readmedium.com/deploying-aws-lambda-layers-with-python-8b15e24bdad2"> <div> <div> <h2>Deploying AWS Lambda layers with Python</h2> <div><h3>Learn to easily deploy a Lambda Layer via AWS CDK and Python.</h3></div> <div><p>medium.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/1*-gTyLrWL-jWj2GGkw63sgg.png)"></div> </div> </div> </a> </div><div id="8d8d" class="link-block"> <a href="https://readmedium.com/deploying-amazon-managed-apache-airflow-with-aws-cdk-7376205f0128"> <div> <div> <h2>Deploying Amazon Managed Apache Airflow with AWS CDK</h2> <div><h3>Learn to easily deploy Apache Airflow as a managed service on AWS using Python and the AWS CDK.</h3></div> <div><p>medium.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/1*sgDEpAFPZfqSBM3DDDvgmQ.png)"></div> </div> </div> </a> </div></article></body>

Building Serverless Services with AWS CDK

Create serverless services in a reliable and reproducible manner using AWS CDK and Python.

Illustration by Freepik Storyset

Serverless computing alleviates operational overhead for developers by allowing cloud providers to handle infrastructure management tasks such as capacity provisioning. Developers can focus on their requirements and business goals, while only paying for the compute resources they use.

AWS Lambda and AWS Fargate are prime examples of serverless services with features like automatic scaling, built-in high availability, and a pay-for-value billing model. These services increase agility, reduce cost, and time to market for the solutions we create.

An efficient way to build and deploy serverless services is through Infrastructure as Code (IaC). IaC can be defined as the process of provisioning and managing your cloud resources programmatically. AWS CloudFormation is a perfect example of tools that enable users to provision resources through IaC.

In this article, we are going to explore how to build and deploy serverless services on AWS using infrastructure as code. We will deploy services such as: AWS API Gateway, AWS Lambda, and AWS Fargate. To deliver infrastructure as code through CloudFormation, we will be using the AWS Cloud Development Kit (AWS CDK) as our framework.

AWS CDK

In simple terms, AWS CDK is a software development framework to define your cloud application resources using familiar programming languages. It provides high-level components called constructs that preconfigure cloud resources with proven defaults. Enabling the provision of resources in a safe, reproducible way through AWS CloudFormation.

A concept such as reproducibility is important. Reproducibility can be described as “the extent to which consistent results are obtained when an experiment is repeated”.

AWS CDK enables reproducibility of infrastructure and services, providing developers with the ability to easily encapsulate and share best practices in infrastructure definition along with their solutions.

AWS Services

Serveless Architecture (Image by author)

A description of each AWS cloud resource used during this article can be found below:

AWS API Gateway: a fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs at any scale.

AWS Lambda: a serverless compute service that lets you run code without provisioning or managing servers, creating workload-aware cluster scaling logic, maintaining event integrations, or managing runtimes.

AWS Elastic Container Service (ECS): a fully managed container orchestration service.

AWS Elastic Container Registry (ECR): a fully managed container registry that makes it easy to store, manage, share, and deploy your container images and artifacts anywhere.

Amazon CloudWatch: a monitoring service that collects operational data in the form of logs, metrics, and events, providing you with a unified view of AWS resources.

Prerequisites

Important: This article will not go through the details of configuring AWS accounts or installing the necessary software applications to enable running AWS CDK. Please refer to AWS CDK Prerequisites for all necessary steps before using AWS CDK.

To work with the AWS CDK, you must have an AWS account and credentials. Additionally, you need to install Node.js and the AWS CDK Toolkit.

Python AWS CDK applications require Python 3.6 or later. If you don’t already have it installed, download a compatible version.

The Python package installer, pip, and virtual environment manager, virtualenv, are also required.

Constructs

AWS CDK — Stack (Image by author)

A construct represents a “cloud component” and encapsulates everything AWS CloudFormation needs to create the component.

AWS CDK creates resources using Constructs. As stated on the AWS website, they are the basic building blocks of AWS CDK apps. A construct represents a “cloud component” and encapsulates everything AWS CloudFormation needs to create the component.

A construct can represent a single resource, such as AWS Lambda function, or it can represent a higher-level component consisting of multiple AWS resources.

Apps and Stacks

A CDK application is commonly called an app, which is represented by the AWS CDK class App. For our CDK application, we will use an example of a data product being delivered using AWS Services. Let’s start by creating our folder structure. Create a directory called “dataproduct”.

mkdir dataproduct

We will use this folder as our main directory:

cd dataproduct

Important: Please keep in mind that you need an empty directory in order to initialize AWS CDK.

Now initialize the project using the “cdk init” command, specifying the desired template (“app”) and programming language.

cdk init app --language python

The previous command generates several files and folders inside the dataproduct directory to help you organize the source code for your AWS CDK application.

After initializing the project, activate the project’s virtual environment. This allows the project’s dependencies to be installed locally in the project folder, instead of globally.

source .venv/bin/activate

Install the app’s standard dependencies:

python -m pip install -r requirements.txt

We can verify there are no issues by listing the stacks in our app with the command below:

cdk ls

You should be able to see on the screen the current stack called dataproduct.

A stack defines a unit of deployment in AWS CDK. As you will see in this article, you can incorporate many resources within a stack and they will be provisioned as a single unit.

Manage AWS Construct Library modules

With CDK, we can use the Python package installer (pip) to install and update AWS Construct Library modules. AWS Construct Library modules are named aws-cdk.SERVICE-NAME.

We will be working with API gateway, Lambda, ECR, and ECS. Let’s proceed to install these libraries using pip:

python -m pip install aws-cdk.aws-lambda aws-cdk.aws-iam aws-cdk.aws-ecr aws-cdk.aws-ecs aws-cdk.aws-apigateway 

Exploring the App

Before creating our stack, we should take a look at some components currently present in our application, such as the App Construct.

The App Construct is used to define a stack within the scope of an application. It's the only construct that can be used as a root for the construct tree.

This construct has been initially created for us when we first executed cdk init and it can be found in the app.py file of the project.

from aws_cdk import core

from dataproduct.dataproduct_stack import DataproductStack


app = core.App()

DataproductStack(app, "dataproduct")

app.synth()

To successfully create some of the components needed in our stack, it is necessary to include an environment. As stated by AWS, an environment is the target AWS account and region into which the stack is intended to be deployed.

Each stack is explicitly or implicitly associated with an AWS account and region.

env = core.Environment(account="yourawsaccount", region="yourawsregion")

Once created, the environment can be provided to the stack.

DataproductStack(app, "dataproduct",  env=env)

After the recent changes, the content of dataproduct/app.py file should like the code below:

Important: Provide your own AWS account and region.

from aws_cdk import core

from dataproduct.dataproduct_stack import DataproductStack


app = core.App()
env = core.Environment(account="yourawsaccount", region="yourawsregion")

DataproductStack(app, "dataproduct",  env=env)

app.synth()

Creating additional directories

We will need 2 additional directories to be created under the dataproduct folder: function and product.

· The function directory will contain the Lambda code.

· The product directory will contain our Dockerfile for the data product.

Let’s create both of them under the main dataproduct directory.

mkdir function product

Lambda Function Code

Create a python file dataproduct-lambda.py under the function directory and include the code below.

This code will enable the lambda function to run a task using the ECS Cluster and Task Definition provided.

Product

As for the product, create a Dockerfile under the product directory and include the line below:

FROM python:3.8-slim-buster

This will build a docker container based on python 3.8.

Creating the stack

We are now ready to add constructs to our stack using the default stack already created for us. It can be found under the main directory in the path:

dataproduct/dataproduct_stack.py

At the moment, it is an empty stack. For the stack to be useful, it will need constructs to be defined within it.

There are different levels of constructs: L1, L2 (low-level constructs, and high-level constructs respectively), and patterns. You can find additional information regarding constructs in https://docs.aws.amazon.com/cdk/latest/guide/constructs.html.

In this article, we will be using L1 and L2 constructs. The code below can be found in the git repo https://github.com/ramonmarrero/demo-dataproduct-cdk.

Important: Replace the vpc and subnet values for your own.

This might look overwhelming at first, specially if it is the first stack you are creating. To clarify the process, I will proceed to explain each resource in the stack in separate sections.

VPC

Let’s start with the first construct we can see in action, the VPC construct. In this case, it is used to retrieve an existing VPC:

ec2.Vpc.from_lookup(scope, id, vpc_id)

ECS Cluster

The ECS Clusterconstruct is part of the ECS module, which contains constructs for working with Amazon Elastic Container Service (Amazon ECS). In this particular case, we are creating a cluster using the VPC previously selected:

ecs.Cluster(scope, id, vpc)

Tags

In the next part, we create some tags for the cluster. Tags are informational key-value elements that you can add to constructs. The Tags.of is used to provide tag information for the resources created.

Tags.of(my_construct).add("key", "value")

ECS Task Definition

The next step is to create an ECS task definition. A task definition is required to run containers in Amazon ECS. The task definition will need permissions to make AWS API calls such as pulling a container image from ECR and permission for the running task to interact with AWS services.

To enable this, 2 roles should be created: an IAM task execution role and a task role.

We create both IAM roles using the Role construct:

iam.Role(scope, id, assumed_by)

Important: pay close attention to the service principal specified in the code.

As stated by AWS, “in IAM policies where the principal is a service, such as Amazon EC2, the service principal name can include the following formats, with “service” replaced by the name of the service”. For instance: service.amazonaws.com .

When creating roles, each service will have its own service principal. It is case sensitive and the format can vary across AWS services. To find the right service principal for your service, AWS recommends to refer to AWS services that work with IAM and look for the services that have Yes in the Service-linked role column.

Now, it is time to create the ECS task definition using the ECS task definition construct and provide both roles previously created.

ecs.TaskDefinition(scope, id, compatibility, cpu, 
memory_mib, execution_role, task_role)

CloudWatch

In order to enable CloudWatch integration with our ECS service, we need to create Log Groups using the LogGroup construct.

logs.LogGroup(scope, id, log_group_name, removal_policy, retention)

ECR

Important: At the time of writing this article, the Docker Image Assets module is experimental and under active development. It is subject to non-backward compatible changes or removal in any future version.

The next step consists of creating a container repository, building a docker image, and pushing the image to the repository. We can accomplish all these tasks using the Docker Image Assets construct:

assets.DockerImageAsset(scope, id, directory, build_args, file=None, repository_name)

Link Services

Once the task definition, docker image, and logging details have been created, we can link them as below:

ecs.TaskDefinition.add_container(id, image, logging)

Additionally, we can grant pull access for the ECS Task Definition:

DockerImageAsset.repository.grant_pull(grantee)

Lambda Role

It is time to create our Lambda resource. Once again, first, we proceed to create the role and policies.

iam.Role(scope, id, assumed_by, managed_policies)

This time, we can add managed policies to the role, such as: AWSLambdaVPCAccessExecutionRole and AWSLambdaBasicExecutionRole

And add policies to the Lambda Role to be able to access the ECS Execution and Task Role previously created.

iam.Role.add_to_policy(statement)

Lambda Function

With the lambda role and policies created, we can now create the Lambda function.

lambda.Function(scope, id, runtime, function_name, role, handler, code, environment )

API Gateway

We will define an API Gateway REST API with AWS Lambda proxy integration as our endpoint.

apigateway.LambdaRestApi(scope, id, handler)

The handler reference the lambda function that handles all requests from this API.

And finally, we can add a lambda dependency to be created after ECS Cluster.

lambda.Function.node.add_dependency(cluster)

With all the necessary resources already created, it is now time to include CloudFormation stack outputs for informational purposes.

# Lambda
core.CfnOutput(
    self, "LambdaResource",
    description="Data Product Lambda Function",
    value=lambda_fargate.function_name
)

# Repository
core.CfnOutput(
    self, "ECRResourceARN",
    description="Data Product Image URI",
    value=dataProductImage.image_uri
)

Synthesize your AWS CloudFormation template

The cdk synth command executes your app, which causes the resources defined in it to be translated to an AWS CloudFormation template.

With more than one stack in your app, you will need to specify which stack(s) to synthesize. In our case, there is only one, therefore the CDK can determine which stack to use.

Let’s synthesize the app by executing the command below:

cdk synth

Deploy the stack

The cdk deploy command deploys the stack(s) to your AWS account.

cdk deploy

Once executing the command, CDK will show on screen all resources present in the stack and about to be deployed.

AWS CDK — Output (Image by author)

If we proceed to deploy the stack, cloudformation will build our product based on the Docker Image Asset specification. After this first step, all resources will be created, only considering dependencies if they have been specified.

By using the URL endpoint provided in the terminal output, we can trigger the process to access the Lambda function and therefore the ECS Fargate service.

AWS CDK — Output (Image by author)

Destroy the stack

The cdk destroy command destroy the stack that was previously deployed to your AWS account.

cdk destroy
AWS CDK — output (Image by author)

Conclusion

In this article, we have built and deployed serverless services on AWS using infrastructure as code. We have leverage AWS CDK, an open software development framework, to provide a reliable and reproducible way of creating and deploying our resources. AWS CDK is a powerful tool that enables us to deliver solutions that include both, infrastructure and product, via familiar programming languages such as python.

Happy coding! Do not forget to follow me or subscribe via email here. You can also support my writing by joining Medium using the following link. For more AWS content

Aws Cdk
AWS
Python
Iac
Serverless
Recommended from ReadMedium