top of page

Tutorial to build Serverless application using the AWS Serverless Application Model

Tutorial to build Serverless application using the AWS Serverless Application Model.


Tutorial Objectives:

1. Learn to build Serverless application using AWS SAM

2. Learn to fully automate builds and deployments by building a CI/CD pipeline using the AWS CDK.

3. Learn to run a Serverless application locally using the SAM CLI.


Prerequisites:

1. Download all the necessary code from here.


Step 1: CREATE A CLOUD9 WORKSPACE

Navigate to the Cloud9 console: https://console.aws.amazon.com/cloud9 Choose Create environment.

Once you navigate to the Cloud9 console, click on the create environment button:

Chose a name for your environment.

Leave the default configuration, we don’t need a heavy server for this workshop.

Once you confirm creation, after a couple of minutes, your environment should look like the following image:

(Optional) If you prefer a dark theme, choose the theme in the top menu: View > Themes > Cloud9 > Cloud 9 Night.

Congratulations you have successfully created your Cloud9 Environment.


Step 2: UPGRADE SAM CLI

We have put together a bootstrap script that will make the upgrade easier for you. Download it by running the following command from your Cloud9 terminal. Download the bootstrap.sh file provided in the prerequisite. Upload the bootstrap.sh file to your cloud9 environment and run the following commands.

chmod +x bootstrap.sh
./bootstrap.sh

THIS MAY TAKE A FEW MINUTES TO COMPLETE.

Example Output:

To verify the new version, Run the following command:

sam --version 

You should see SAM CLI, version 0.43.0 or greater.


Step 3: Create a new SAM Application

To initialize a new SAM Application Project, run the following command.

sam init

It will prompt for project configuration parameters:

Type 1 to select AWS Quick Start Templates

Type 1 to select Zip as the package type.

Type 14 to select the Java 8 runtime.

Type 1 to select maven

Press enter to accept the default sam-app for project name.

Type 1 to select the Hello World Example.

Project should now be initialized.

You should see a new folder sam-app created with a basic Hello World scaffolding.


Step 4: RUN PROJECT LOCALLY

Install Dependencies

Before we run the application locally, it’s a common practice to install third-party libraries or dependencies that your application might be using. In our case we need to upgrade Java and install Maven.

Install OpenJDK 8. To do this, run the yum tool with the install command, specifying the OpenJDK 8 package.

sudo yum -y install java-1.8.0-openjdk-devel

Switch or upgrade the default Java development toolset to OpenJDK 8. To do this, run the update-alternatives command with the –config option. Run this command twice to switch or upgrade the command-line versions of the Java runner and compiler.

sudo update-alternatives --config java 
sudo update-alternatives --config javac

Install Maven by using the terminal to run the following commands.

sudo wget http://repos.fedorapeople.org/repos/dchen/apache-maven/epel-apache-maven.repo -O /etc/yum.repos.d/epel-apache-maven.repo 

sudo sed -i s/\$releasever/7/g /etc/yum.repos.d/epel-apache-maven.repo 

sudo yum install -y apache-maven

We can now use SAM to build our project.

cd ~/environment/sam-app 
sam build

Example Output:

RUN USING SAM CLI

There are 2 ways of running a Serverless app locally: 1) By invoking an individual Lambda function or 2) By running a local HTTP server that simulates API Gateway. For this workshop, we will focus on number 2, but you can learn about invoking individual functions in the SAM Local Invoke reference.


In the terminal, run the following command from the root directory of the sam-app folder:

cd ~/environment/sam-app 
sam local start-api --port 8080

Test your endpoint

Once your local server is running, we can send HTTP requests to test it.

Without killing the running process, open a new terminal.

Test your endpoint by running a CURL command that triggers an HTTP GET request.

curl http://localhost:8080/hello

MAKE A CODE CHANGE

Now let's make a simple code change. Press CTRL-C to terminate the running process.

Open the file sam-app/HelloWorldFunction/src/main/java/helloworld/App.java change the response message to return hello beautiful world instead of hello world. Your Lambda handler should look like this after the change:

Open the file sam-app/HelloWorldFunction/src/test/java/helloworld/AppTest.java and update the expected value for the response to match the new message. The unit test should look like this after the update:

To see the new changes we must rebuild our SAM project.

sam build

Example Output

Then rerun our local deployment.

sam local start-api --port 8080

Now rerun the CURL command to see the changes reflected in your endpoint.

Congratulations, your project is working successfully when tested locally.


Step 5: Deploy to AWS.

Build the Application.

To build a SAM project, we are going to use the sam build command. This command iterates through the functions in your application, looking for the manifest file (such as requirements.txt or package.json) that contains the dependencies, and automatically creates deployment artifacts.

From the root of the sam-app folder, run the following command in the terminal:

cd ~/environment/sam-app 
sam build

Build completed

When the build finishes successfully, you should see a new directory created in the root of the project named .aws-sam. It is a hidden folder, so if you want to see it in the IDE, make sure you enable Show hidden files in Cloud9 to see it.

Explore the build folder

Take a moment to explore the content of the build folder. Notice that the unit tests are automatically excluded from it.

DEPLOY THE APP

The sam deploys command deploys your application by launching a CloudFormation stack. This command has a guided interactive mode, which you enable by specifying the –guided parameter. It is recommended to deploy with guided mode for the first time as it will capture the configuration for future deployments.

Run the following command in the same directory level where the template.yaml is located:

sam deploy --guided

It will ask for certain input parameters, like so:

Confirm deployment

At some point, SAM will ask for deployment confirmation. This is possible because it first creates a CloudFormation ChangeSet, and then it asks for confirmation to execute it. This is kind of like a dry run deployment and is a best practice when doing deployments via CloudFormation. Type y to confirm.

Deployment completed

This command might take a few minutes to finish because it is creating the resources (Lambda function, API Gateway and IAM roles) on the AWS account. When it completes successfully, you should see an output similar to the following:

To understand what just happened we would have to take a look at the guided deployment of the SAM application.

The guided deployment does a few things for you. Let’s take a quick look at what happened under the hood during the guided deployment to understand this process better.

1) Your codebase gets packaged in a zip file. 2) SAM creates an S3 bucket in your account if it doesn’t already exist. 3) Zip file is uploaded to the S3 bucket. 4) SAM creates the packaged template that references the location of the zip file on S3. 5) This template is also uploaded to the S3 bucket. 6) SAM starts the deployment via CloudFormation ChangeSets.

The first time you do a guided deployment, a new file samconfig.toml is created in the root of your project with your specified deployment parameters, this is so that the next time you execute sam deploy, it uses the same parameters without having you enter them again.

VERIFY DEPLOYMENT

Now that your Serverless application is deployed to an AWS account, let’s check if it is running as expected.

Open the CloudFormation console

Navigate to the AWS CloudFormation console, make sure you are in the same region where you have been working on so far. You should see the new stack sam-app in the CREATE_COMPLETE status.

Test the app

Click on the sam-app stack and then go to the Outputs tab. In this tab, you will see the API Gateway URL, the Lambda function ARN and the IAM Role ARN for the function. To test our application, copy the API Gateway endpoint URL and paste it into a new browser window.

When you navigate to this URL in the browser, API Gateway is invoking the Hello World Lambda function. If everything works, you will see Lambda response:

Congratulations!

You have successfully deployed a Serverless application! Let’s automate deployments now!


Step 6: BUILD THE PIPELINE

In this step we will learn how to create a CI/CD pipeline for automatic deployment of the serverless application.


CREATE A GIT REPOSITORY

Any CI/CD pipeline starts with a code repository. In this workshop we use AWS CodeCommit for ease of integration, but you could use other source code integrations, like GitHub for example.

Run the following command from your terminal to create a new CodeCommit repository:

aws codecommit create-repository --repository-name sam-app

You should see the following output. Copy the value of cloneUrlHttp, you will need it later.

CONFIGURE CREDENTIALS

One of the cool things about CodeCommit is the support for IAM authentication. And if you are running this workshop from a Cloud9 workspace, you can leverage the fact that your terminal is already pre-authenticated with valid AWS credentials.

Configure the git client with username and email, so your commits have an author defined.

git config --global user.name "Replace with your name" 
git config --global user.email "replace_with_your_email@example.com"

PUSH THE CODE

Ignore the build artifacts

There is no need to track the .aws-sam directory or the packaged.yaml under version control as they are re-generated on every build. We will paste the following into a .gitgnore file.

Create the file

cd ~/environment/sam-app 
touch .gitignore

In Cloud9, remember to enable hidden files:

Open the .gitignore file and paste the two lines below.

.aws-sam/
packaged.yaml

Your .gitignore file should look like this.

Remember to save the file.

From the root directory of your sam-app project, run the following commands:

cd ~/environment/sam-app 
git init 
git add . 
git commit -m "Initial commit"

Example Output:

Push the code

Add your CodeCommit repository URL as a remote on your local git project.

git remote add origin codecommit://sam-app

Now, push the code:

git push -u origin master

Verify in CodeCommit

Navigate to the AWS CodeCommit console, find your sam-app repository and click on it to view its contents. Make sure your code is there. You should see a screen like the following:

Introducing the AWS CDK

In this workshop, we are going to use the AWS Cloud Development Kit (also known as CDK), as the pipeline vending mechanism. The AWS CDK is a software development framework for defining cloud infrastructure in code and provisioning it through AWS CloudFormation.

That’s right! You can describe your infrastructure by writing code in TypeScript, C#, Python or Java. Your code is then synthesized into CloudFormation and using the CDK CLI you can deploy it to an AWS account.


SETUP A CDK PROJECT

Install the latest CDK

If you are using Cloud9, the CDK is already pre-installed but it will likely be a few versions old. Run the following commands from the Cloud9 terminal to remove your current version and install the latest one:

npm uninstall -g aws-cdk 
npm install -g aws-cdk --force

Initialize project

Now, let’s create a folder within our sam-app directory where the pipeline code will reside.

cd ~/environment/sam-app 
mkdir pipeline 
cd pipeline

Initialize a new CDK project within the pipeline folder by running the following command:

cdk init --language java

After a few seconds, our new CDK project should look like this:

Project structure

At this point, your project should have the structure below (only the most relevant files and folders are shown). Within the CDK project, the main file you will be interacting with is the PipelineStack.java. Don’t worry about the rest of the files for now.

Modify stack name

Open the src/main/java/com/myorg/PipelineApp.java file, which is your entry point to the CDK project, and change the name of the stack to sam-app-cicd.

Save the file.


Create a PIPELINE using CODE

Open the file src/main/java/com/myorg/PipelineStack.java in your Cloud9 workspace. It is empty at the moment, but here is where you will be adding code to build your CI/CD pipeline.

Build the CDK project

Even though we haven’t wrote any code yet, let’s get familiar with how to build and deploy a CDK project, as you will be doing it multiple times in this workshop and you should get comfortable with the process. Start by building the project with the following command:

cd ~/environment/sam-app/pipeline 
mvn package

Deploy a CDK project

After the build has finished, go ahead and deploy the pipeline project by using the CDK CLI:

cdk bootstrap
cdk deploy

The output should look like the following:

A new CloudFormation stack was created in your account, but because your CDK project is empty, the only resource that was created was an AWS::CDK::Metadata. If you check your CloudFormation Console, you will see the new stack and the metadata resource.


ARTIFACTS BUCKET

Every Code Pipeline needs an artifacts bucket, also known as Artifact Store. CodePipeline will use this bucket to pass artifacts to the downstream jobs and its also where SAM will upload the artifacts during the build process.

Let’s get started and write the code for creating this bucket:

Make sure you are editing the PipelineStack.java file.

Alternatively, you can copy the following code and replace it with the existing code.

package com.myorg;

import software.constructs.Construct;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.s3.Bucket;

public class PipelineStack extends Stack {
    public PipelineStack(final Construct scope, final String id) {
        this(scope, id, null);
    }

    public PipelineStack(final Construct scope, final String id, final StackProps props) {
        super(scope, id, props);

        Bucket artifactsBucket = new Bucket(this, "ArtifactsBucket");
    }
}

Your PipelineStack.java file should look like this:

Setting up the SOURCE STAGE

The Source Stage is the first step of any CI/CD pipeline and it represents your source code. This stage is in charge of triggering the pipeline based on new code changes (i.e. git push or pull requests). In this workshop, we will be using AWS CodeCommit as the source provider, but CodePipeline also supports S3, GitHub and Amazon ECR as source providers.

Append the following code snippet after your bucket definition in the PipelineStack.java file:

IRepository codeRepo = Repository.fromRepositoryName(this, "AppRepository", "sam-app");

Pipeline pipeline = new Pipeline(this, "Pipeline", PipelineProps.builder()
        .artifactBucket(artifactsBucket).build());

Artifact sourceOutput = new Artifact("sourceOutput");

CodeCommitSourceAction codeCommitSource = new CodeCommitSourceAction(CodeCommitSourceActionProps.builder()
        .actionName("CodeCommit_Source")
        .repository(codeRepo)
        .output(sourceOutput)
        .build());

pipeline.addStage(StageOptions.builder()
        .stageName("Source")
        .actions(Collections.singletonList(codeCommitSource))
        .build());

You will also need the following imports. We’ll add all the imports we will need now for the workshop.

import software.amazon.awscdk.services.codebuild.*;
import software.amazon.awscdk.services.codecommit.*;
import software.amazon.awscdk.services.codepipeline.*;
import software.amazon.awscdk.services.codepipeline.actions.*;
import java.util.*;
import static software.amazon.awscdk.services.codebuild.LinuxBuildImage.AMAZON_LINUX_2;

To view the complete code see the PipelineStack_Part1.java file from the downloaded resources.


Since we already have the CodeCommit repository, we don’t need to create a new one, we just need to import it using the repository name.

Also notice how we define an object sourceOutput as a pipeline artifact; This is necessary for any files that you want CodePipeline to pass to downstream stages. In this case, we want our source code to be passed to the Build stage.


Setting up the BUILD STAGE

The Build Stage is where your Serverless application gets built and packaged by SAM. We are going to use AWS CodeBuild as the Build provider for our pipeline. It is worth mentioning that CodePipeline also supports other providers like Jenkins, TeamCity or CloudBees.

Add the build stage

Let’s go ahead and add a Build stage to you PipelineStack.java:


// Declare build output as artifacts
Artifact buildOutput = new Artifact("buildOutput");

// Declare a new CodeBuild project
PipelineProject buildProject = new PipelineProject(this, "Build", PipelineProjectProps.builder()
        .environment(BuildEnvironment.builder()
                .buildImage(AMAZON_LINUX_2).build())
        .environmentVariables(Collections.singletonMap("PACKAGE_BUCKET", BuildEnvironmentVariable.builder()
                .value(artifactsBucket.getBucketName())
                .build()))
        .build());

// Add the build stage to our pipeline
CodeBuildAction buildAction = new CodeBuildAction(CodeBuildActionProps.builder()
        .actionName("Build")
        .project(buildProject)
        .input(sourceOutput)
        .outputs(Collections.singletonList(buildOutput))
        .build());

pipeline.addStage(StageOptions.builder()
        .stageName("Build")
        .actions(Collections.singletonList(buildAction))
        .build());

To view the complete code see the PipelineStack_Part2.java file from the downloaded resources.


Deploy the pipeline

From your terminal, run the following commands to deploy the pipeline:

mvn package 
cdk deploy

Verify pipeline creation

Navigate to the AWS CodePipeline Console and click on your newly created pipeline!

The Build step should have failed. Don’t worry! this is expected because we haven’t specified what commands to run during the build yet, so AWS CodeBuild doesn’t know how to build our Serverless application.

Create the BUILDSPEC FILE

A Buildspec File is a series of commands in YAML format that CodeBuild executes to build your application. This file is placed in the root folder of a SAM application and CodeBuild will automatically find it and run it during build time.

In your Cloud9 editor, create a new file named buildspec.yml in the root (top level) of the sam-app directory by right clicking on the sam-app folder and selecting New file.

Then, paste the following content into the file:


# ~/environment/sam-app/buildspec.yml

version: 0.2
phases:
  install:
    runtime-versions:
      java: corretto8
    commands:
      # Install packages or any pre-reqs in this phase.
      # Upgrading SAM CLI to latest version
      - pip3 install --upgrade aws-sam-cli
      - sam --version

  build:
    commands:
      # Use Build phase to build your artifacts (compile, etc.)
      - cd HelloWorldFunction
      - mvn package
      - cd ..
      - sam build

  post_build:
    commands:
      # Use Post-Build for notifications, git tags, upload artifacts to S3
      - sam package --s3-bucket $PACKAGE_BUCKET --output-template-file packaged.yaml

artifacts:
  discard-paths: yes
  files:
    # List of local artifacts that will be passed down the pipeline
    - packaged.yaml

Save the file. It should look like the following screenshot.

Take a moment to understand the structure of the file and feel free to read the Buildsec Reference here: https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html.


Push code changes

Commit your changes and push them to the repository.

cd ~/environment/sam-app 
git add . 
git commit -m "Added buildspec.yml" 
git push

Verify build succeeds

Navigate to your CodePipeline again, and wait for it to trigger automatically. This time the build will succeed:


Creating the DEPLOY STAGE

The Deploy Stage is where your SAM application and all its resources are created in an AWS account. The most common way to do this is by using CloudFormation ChangeSets to deploy. This means that this stage will have 2 actions: the CreateChangeSet and the ExecuteChangeSet.

Add the Deploy stage to your PipelineStack.java:

// Deploy stage
CloudFormationCreateReplaceChangeSetAction createChangeSet = new CloudFormationCreateReplaceChangeSetAction(CloudFormationCreateReplaceChangeSetActionProps.builder()
        .actionName("CreateChangeSet")
        .templatePath(buildOutput.atPath("packaged.yaml"))
        .stackName("sam-app")
        .adminPermissions(true)
        .changeSetName("sam-app-dev-changeset")
        .runOrder(1)
        .build());

CloudFormationExecuteChangeSetAction executeChangeSet = new CloudFormationExecuteChangeSetAction(CloudFormationExecuteChangeSetActionProps.builder()
        .actionName("Deploy")
        .stackName("sam-app")
        .changeSetName("sam-app-dev-changeset")
        .runOrder(2)
        .build());

pipeline.addStage(StageOptions.builder()
        .stageName("Dev")
        .actions(Arrays.asList(createChangeSet, executeChangeSet))
        .build());

To view the complete code see the PipelineStack_Part3.java file from the downloaded resources.


Deploy the pipeline

On your terminal, run the following commands from within the pipeline directory:

cd ~/environment/sam-app/pipeline 
mvn package 
cdk deploy

The CLI might ask you to confirm the changes before deploying, this is because we are giving Admin permissions to the IAM role that deploys our application. This is generally not a bad practice since this role can only be assumed by CloudFormation and not by a human, however, if your organization has a stricter security posture you may want to consider creating a custom IAM deployment role with a fine grain policy.


Trigger a release

Navigate to your pipeline and you will see the Deploy stage has been added, however, it is currently greyed out because it hasn’t been triggered. Let’s just trigger a new run of the pipeline manually by clicking the Release Change button.


VERIFY PIPELINE

Let your pipeline run every stage. After it finishes it will look all green like the following screenshot:

Congratulations! You have created a CI/CD pipeline for a Serverless application!



Step 8: CANARY DEPLOYMENTS

A Canary Deployment is a technique that reduces the risk of deploying a new version of an application by slowly rolling out the changes to a small subset of users before rolling it out to the entire customer base.


UPDATE SAM TEMPLATE

Open the SAM template (sam-app/template.yaml) in your project and add the following lines to the HelloWorldFunction properties section.

AutoPublishAlias: live 
DeploymentPreference: 
        Type: Canary10Percent5Minutes

It should look like this: PLEASE CHECK THE CORRECT INDENTATION, IT IS VERY IMPORTANT IN YAML FORMAT.

Deployment Preference Types

For this workshop, we are using the Canary10Percent5Minutes strategy, which means that traffic is shifted in two increments. In the first increment, only 10% of the traffic is shifted to the new Lambda version, and after 5 minutes, the remaining 90% is shifted. There are other deployment strategies you can choose in CodeDeploy:

  • Canary10Percent30Minutes

  • Canary10Percent5Minutes

  • Canary10Percent10Minutes

  • Canary10Percent15Minutes

  • Linear10PercentEvery10Minutes

  • Linear10PercentEvery1Minute

  • Linear10PercentEvery2Minutes

  • Linear10PercentEvery3Minutes

  • AllAtOnce

The Linear strategy means that traffic is shifted in equal increments with an equal number of time interval between each increment.


Validate the SAM template

Run the following command on your terminal:

cd ~/environment/sam-app 
sam validate

If the template is correct, you will see template.yaml is a valid SAM Template. If you see an error, then you likely have an indentation issue on the YAML file. Double check and make sure it matches the screenshot shown above.


Push the changes

In the terminal, run the following commands from the root directory of your sam-app project.


git add . 
git commit -m "Canary deployments with SAM" 
git push

MONITOR CANARY HEALTH

Canary deployments are considerably more successful if the code is being monitored during the deployment. You can configure CodeDeploy to automatically roll back the deployment if a specified CloudWatch metric has breached the alarm threshold. Common metrics to monitor are Lambda Invocation errors or Invocation Duration (latency), for example.


Define a CloudWatch Alarm

Add the following alarm definition to the template.yaml file in the Resources section after the HelloWorldFunction definition.

CanaryErrorsAlarm:
  Type: AWS::CloudWatch::Alarm
  Properties:
    AlarmDescription: Lambda function canary errors
    ComparisonOperator: GreaterThanThreshold
    EvaluationPeriods: 2
    MetricName: Errors
    Namespace: AWS/Lambda
    Period: 60
    Statistic: Sum
    Threshold: 0
    Dimensions:
      - Name: Resource
        Value: !Sub "${HelloWorldFunction}:live"
      - Name: FunctionName
        Value: !Ref HelloWorldFunction
      - Name: ExecutedVersion
        Value: !GetAtt HelloWorldFunction.Version.Version

And then add the following lines to the DeploymentPreference section of the HelloWorldFunction definition

Alarms:
    - !Ref CanaryErrorsAlarm

To view the complete code see the template.yaml file from the downloaded resources.

Validate the SAM template

Run the following command on your terminal:

cd ~/environment/sam-app 
sam validate

If the template is correct, you will see template.yaml is a valid SAM Template. If you see an error, then you likely have an indentation issue on the YAML file. Double check and make sure it matches the screenshot shown above.


Push the changes

In the terminal, run the following commands from the root directory of your sam-app project.

git add . 
git commit -m "Added CloudWatch alarm to monitor the canary" 
git push

VERIFY IN CODEDEPLOY

Wait for your pipeline to get to the deployment stage (ExecuteChangeSet) and when you see it In Progress. Navigate to the CodeDeploy console to watch the deployment progress.

Navigate to the AWS CodeDeploy console and after a couple of minutes, you should see a new deployment in progress. Click on the Deployment to see the details.


The deployment status shows that 10% of the traffic has been shifted to the new version (aka The Canary). CodeDeploy will hold the remaining percentage until the specified time interval has ellapsed, in this case, we specified the interval to be 5 minutes.

Shortly after the 5 minutes, the remaining traffic should be shifted to the new version:


ROLLBACKS

Monitoring the health of your canary allows CodeDeploy to make a decision to whether a rollback is needed or not. If any of the CloudWatch Alarms specified gets to ALARM status, CodeDeploy rollsback the deployment automatically.

Introduce an error on purpose

Lets break the Lambda function on purpose so that the CanaryErrorsAlarm gets triggered during deployment.

Update the lambda code in /samapp/HelloWorldFunction/src/main/java/helloworld/App.java to throw an error on every invocation, like this:

package helloworld;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;

/**
 * Handler for requests to Lambda function.
 */
public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

    public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
        throw new Error("This will cause a deployment rollback");
        // Map<String, String> headers = new HashMap<>();
        // headers.put("Content-Type", "application/json");
        // headers.put("X-Custom-Header", "application/json");

        // APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent()
        //         .withHeaders(headers);
        // try {
        //     final String pageContents = this.getPageContents("https://checkip.amazonaws.com");
        //     String output = String.format("{ \"message\": \"hello beautiful world\", \"location\": \"%s\" }", pageContents);

        //     return response
        //             .withStatusCode(200)
        //             .withBody(output);
        // } catch (IOException e) {
        //     return response
        //             .withBody("{}")
        //             .withStatusCode(500);
        // }
    }

    private String getPageContents(String address) throws IOException{
        URL url = new URL(address);
        try(BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
            return br.lines().collect(Collectors.joining(System.lineSeparator()));
        }
    }
}

Make sure to update the unit test, otherwise the build will fail. Comment out every line in the /sam-app/HelloWorldFunction/src/test/java/helloworld/AppTest.java file:

package helloworld;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;

public class AppTest {
  @Test
  public void successfulResponse() {
    // App app = new App();
    // APIGatewayProxyResponseEvent result = app.handleRequest(null, null);
    // assertEquals(result.getStatusCode().intValue(), 200);
    // assertEquals(result.getHeaders().get("Content-Type"), "application/json");
    // String content = result.getBody();
    // assertNotNull(content);
    // assertTrue(content.contains("\"message\""));
    // assertTrue(content.contains("\"hello beautiful world\""));
    // assertTrue(content.contains("\"location\""));
  }
}

Push the changes

In the terminal, run the following commands from the root directory of your sam-app project.

git add . 
git commit -m "Breaking the lambda function on purpose" 
git push

WAIT FOR DEPLOYMENT TO START

Again, wait for your Pipeline to reach the deployment phase (ExecuteChangeSet). It should turn blue when it begins:

INVOKE THE CANARY

While the deployment is running, you need to generate traffic to the new Lambda function to make it fail and trigger the CloudWatch Alarm. In a real production environment, your users will likely generate organic traffic to the canary function, so you may not need to do this.

In your terminal, run the following command to invoke the Lambda function:

aws lambda invoke --function-name \
$(aws lambda list-functions | jq -r -c '.Functions[] | select( .FunctionName | contains("sam-app-HelloWorldFunction")).FunctionName'):live \
--payload '{}' \
response.json

Note: If you get an error that jq command is not installed, you can install it by running sudo yum install -y jq.


There will be a new file response.json created. It contains the response of the lambda invocation. If you open it, you may see the the response of the old Lambda version, or you may see the new one that causes an error.

Remember: During deployment, only 10% of the traffic will be routed to the new version. So, keep on invoking your lambda many times. 1 out of 10 invocations should trigger the new broken lambda, which is what you want to cause a rollback.

Here is a command that invokes your function 15 times in a loop. Feel free to run it in your terminal.

counter=1
while [ $counter -le 15 ]
do
    aws lambda invoke --function-name \
    $(aws lambda list-functions | jq -r -c '.Functions[] | select( .FunctionName | contains("sam-app-HelloWorldFunction")).FunctionName'):live \
    --payload '{}' \
    response.json
    sleep 1
    ((counter++))
done

WATCH CODEDEPLOY ROLLBACK


Navigate to the AWS CodeDeploy Console and go into the deployment In-Progress to view its details.

After a few minutes, CodeDeploy will detect that the CanaryErrorsAlarm has triggered and it will start rolling back the deployment. The screen will look something like this:

Congratulations, You have successfully set you a CI/CD pipeline with Canary Deployment and Fault Tolerance.


Step 9: Clean Up.

DELETE S3 BUCKETS

There are 2 buckets that got created as part of this workshop, one was created by SAM CLI and the other one was created by CodePipeline. To delete these buckets, we should empty them first and then proceed to delete them.

First Bucket

Empty the bucket by going to the S3 console. And then Delete it.

Second Bucket

Empty the bucket by going to the S3 console. And then Delete it.

DELETE CloudFormation STACKS

Now that the buckets are empty, we can delete the Cloudformation stacks:

Delete the CF stacks in following sequence and please wait for each to complete before going to next one.

Delete sam-app

aws cloudformation delete-stack --stack-name sam-app

Delete sam-app-cicd

aws cloudformation delete-stack --stack-name sam-app-cicd

Delete aws-sam-cli-managed-default

aws cloudformation delete-stack --stack-name aws-sam-cli-managed-default

You can also delete the CF Stacks by navigation to the Cloud Formation management console and deleting the stacks.


Finally Delete the cloud 9 environment.


Thank you for doing this tutorial!

We hope you learned something.


Was