Automating Penetration Testing in a CI/CD Pipeline (Part 2)

Continuous Security: Security in the Continuous Delivery Pipeline is a series of articles addressing security concerns and testing in the Continuous Delivery pipeline. This is the sixth article in the series.

In the first post, we discussed what OWASP ZAP is, how it’s installed and automating that installation process with Ansible. This second article of three will drill down into how to use the ZAP server, created in Part 1 for penetration testing your web-based application.

Penetration Test Script

If you recall the flow diagram (below) from the first post, we will need a way to talk to ZAP so that it can trigger a test against our application. To do this we’ll use the available ZAP API and wrap up the API in a Python script. The script will allow us to specify our ZAP server, target application server, trigger each phase of the penetration test and report our results.

ZAP-Basic-CI_CD-Flow - New Page (1)

The core of the ZAP API is to open our proxy, access the target application, spider the application, run an automated scan against it and fetch the results. This can be accomplished with just a handful of commands; however, our goal is to eventually get this bound into a CI/CD environment, so the script will have to be more versatile than a handful of commands.

The Python ZAP API can be easily installed via pip:

pip install python-owasp-zap-v2.4

We’ll start by breaking down what was outlined in the above paragraph. For learning purposes, these can be easily ran from the Python command line.

from zapv2 import ZAPv2

target = "http://" % target_application_url
zap = ZAPv2(proxies={'http': "http://%s" %zap_hostname_or_ip,
                     'https': "https://%s" %zap_hostname_or_ip}
zap.urlopen(target)
zap.spider.scan(target)
zap.spider.static()
# when status is >= 100, the spider has completed and we can run our scan
zap.ascan.scan(target)
zap.ascan.status()
# when status is >= 100, the scan has completed and we can fetch results
print zap.core.alerts()

This snippet will print our results straight to STDOUT in a mostly human readable format. To wrap all this up so that we can easily integrate this into an automated environment we can easily change our output to JSON, accept incoming parameters for our ZAP host names and target url. The following script takes the above commands and adds the features just mentioned.

The script can be called as follows:

./pen-test-app.py --zap-host zap_host.example.com:8080 --target app.example.com

Take note, the server that is launching our penetration test does not need to run ZAP itself, nor does it need to run the application we wish to run our pen test against.

Lets set up a very simple web-based application that we can use to test against. This isn’t a real-world example but it works well for the scope of this article. We’ll utilize Flask, a simple Python-based http server and allow it run a basic application that will simply display what was typed into the form field once submitted. The script can be downloaded here.

First Flask needs to be installed and the server started with the following:

pip install flask
python simple_server.py

The server will run on port 5000 over http. Using the example command above, we’ll run our ZAP penetration test against it as so:

/pen-test-app.py --zap-host 192.168.1.5:8080 --target http://192.168.1.73:5000
Accessing http://192.168.1.73:5000
Spidering http://192.168.1.73:5000
Spider completed
Scanning http://192.168.1.73:5000
Info: Scan completed; writing results.

Please note that the ZAP host is simply a url and a port, while the target must specify the protocol, either ‘http’ or ‘https’.

The ‘pen-test-app.py’ script is just an example of one of the many ways OWASP ZAP can be used in an automated manner. Tests can also be written to integrate FireFox (with ZAP as its proxy) and Selenium to mimic user interaction with your application. This could also be ran from the same script in addition to the existing tests.

Scan and Report the Results

The ZAP API will return results to the ‘pen-test-app.py’ script which in turns will write them to a JSON file, ‘results.json’. These results could be easily scanned for risk severities such as “grep -ie ‘high’ -e ‘medium’ results.json”. This does not give us much granularity in determining which tests are reporting errors nor if they critical enough to fail an entire build pipeline.

This is where a tool called Behave comes into play. Behave is a Gerkin-based language that allows the user to write test scenarios in a very human readable format.

Behave can be easily installed with pip:

pip install behave

Once installed our test scenarios are placed into a feature file. For this example we can create a file called ‘pen_test.feature’ and create a scenario.

Feature: Pen test the Application
  Scenario: The application should not contain Cross Domain Scripting vulnerabilities
    Given we have valid json alert output
    When there is a cross domain source inclusion vulnerability
    Then none of these risk levels should be present
      | risk |
      | Medium |
      | High |

The above scenario gets broken down into steps. The ‘Given’, ‘When’ and ‘Then’ will each correlate to a portion of Python code that will test each statement. The ‘risk’ portion is a table, that will be passed to our ‘Then’ statement. This can be read as “If the scanner produced valid JSON, succeed if there are no CSX vulnerabilities or only ones with ‘Low’ severity.

With the feature file in place, each step must now be written. A directory must be created called ‘steps’. Inside the ‘steps’ directory we create a file with the same name as the feature file but with a ‘.py’ extension instead of a ‘.feature’ extension. The following example contains the code for each step above to produce a valid test scenario.

import json
import re
import sys

from behave import *

results_file = 'results.json'

@given('we have valid json alert output')
def step_impl(context):
    with open(results_file, 'r') as f:
        try:
            context.alerts = json.load(f)
        except Exception as e:
            sys.stdout.write('Error: Invalid JSON in %s: %s\n' %
                             (results_file, e))
            assert False

@when('there is a cross domain source inclusion vulnerability')
def step_impl(context):
    pattern = re.compile(r'cross(?:-|\s+)(?:domain|site)', re.IGNORECASE)
    matches = list()

    for alert in context.alerts:
        if pattern.match(alert['alert']) is not None:
             matches.append(alert)
    context.matches = matches
    assert True

@then('none of these risk levels should be present')
def step_impl(context):
    high_risks = list()

    risk_list = list()
    for row in context.table:
        risk_list.append(row['risk'])

    for alert in context.matches:
         if alert['risk'] in risk_list:
             if not any(n['alert'] == alert['alert'] for n in high_risks):
                 high_risks.append(dict({'alert': alert['alert'],
                                          'risk': alert['risk']}))

    if len(high_risks) > 0:
        sys.stderr.write("The following alerts failed:\n")
    for risk in high_risks:
        sys.stderr.write("\t%-5s: %s\n" % (risk['alert'], risk['risk']))
        assert False

    assert True

To run the above test simply type ‘behave’ from the command line.

behave
 
Feature: Pen test the Application # pen_test.feature:1

  Scenario: The application should not contain Cross Domain Scripting vulnerabilities # pen_test.feature:7
    Given we have valid json alert output # steps/pen_test.py:14 0.001s
    When there is a cross domain source inclusion vulnerability # steps/pen_test.py:25 0.000s
    Then none of these risk levels should be present # steps/pen_test.py:67 0.000s
      | risk |
      | Medium |
      | High |

1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
3 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.001s

We can clearly see what was ran and each result. If this was ran from a Jenkins server, the return code will be read and the job will succeed. If a step fails, behave will return non-zero, triggering Jenkins to fail the job. If the job fails, it’s up to the developer to investigate the pipeline, find the point it failed, login to the Jenkins server and view the console output to see which test failed. This may not be the most ideal method. We can tell behave that we want our output in JSON so that another script can consume the JSON, reformat it into something an existing reporting mechanism could use and upload it to a central location.

To change behave’s behavior to dump JSON:

behave --no-summary --format json.pretty > behave_results.json

A reporting script can either read the behave_results, json file or read the STDIN pipe directly from behave. We’ll discuss more regarding this in the followup post.

Summary

If you’ve been following along since the first post, we have learned how to set up our own ZAP service, have the ZAP service penetration test a target web application and examine the results. This may be a suitable scenario for many systems. However, integrating this into a full CI/CD pipeline would be the optimal and most efficient use of this.

In part three we will delve into how to fully integrate ZAP so that not only will your application involve user, acceptance and capacity testing, it will now pass through security testing before reaching your end users.

Stelligent is hiring! Do you enjoy working on complex problems like security in the CD pipeline? Do you believe in the “everything-as-code” mantra? If your skills and interests lie at the intersection of DevOps automation and the AWS cloud, check out the careers page on our website.

Serverless Delivery: Orchestrating the Pipeline (Part 3)

In the second post of this series, we focused on how to get our serverless application running with Lambda, API Gateway and S3. Our application is now able to run on a serverless platform, but we still have not applied the fundamentals of continuous delivery that we talked about in the first part of this series.

In this third and final part of this series on serverless delivery, we will implement a continuous delivery pipeline using serverless technology. Our pipeline will be orchestrated by CodePipeline with actions implemented in Lambda functions. The definition of the CodePipeline resource as well as the Lambda functions that support it are all defined in the same CloudFormation stack that we looked at last week.

Visualize The Goal

To help visualize what we are building, here is a picture of what the final pipeline looks like.

codepipeline

If you’re new to CodePipeline, let’s go over a few important terms:

  • Job – An execution of the pipeline.
  • Stage – A group of actions in the pipeline. Stages never run in parallel to each other and only one job can actively be running in a stage at a time. If a stage is currently running and a new job arrives at the stage it will block until the prior job completes. If multiple new jobs arrive, only the newest will be run while the rest will be dropped.
  • Action – A step to be performed. Actions can be in parallel or series to each other. In this pipeline, all our actions will be implemented by Lambda invocations.
  • Artifact – Each action can declare input and output artifacts that will be stored in an S3 bucket. These are objects that it will either expect to have before it runs, or objects that it will produce and make available after it runs.

The pipeline we have built for our application consists of the following four stages:

  • Source – The source stage has only one action to acquire the source code for later stages.
  • Commit – The commit stage has two actions that are responsible for:
    • Resolving project dependencies
    • Processing (e.g., compile, minify, uglify) the source code
    • Static analysis of the code
    • Unit testing of the application
    • Packaging the application for subsequent deployment
  • Acceptance – The acceptance stage has actions that will:
    • Update the Lambda function from latest source
    • Update S3 bucket with latest artifacts
    • Update API Gateway
    • End-to-end testing of the application
  • Production – The production stage performs the same steps as the Acceptance stage but against the production Lambda, S3 and API Gateway resources

Here is a more detailed picture of the pipeline. We will spend the rest of this post breaking down each step of the pipeline.

pipeline-overview

Start with Source Stage

Diagram Step: 1

The source stage only has one action in it, a 3rd party action provided by GitHub. The action will register a hook with the repo that you provide to kickoff a new job for the pipeline whenever code is pushed to the GitHub repository. Additionally, the action will pull the latest code from the branch you specified and zip it up into an object in an S3 bucket for later actions to reference.

{
  "Name": "Source",
  "Actions": [
    {
      "InputArtifacts": [],
      "Name": "Source",
      "ActionTypeId": {
        "Category": "Source",
        "Owner": "ThirdParty",
        "Version": "1",
        "Provider": "GitHub"
      },
      "Configuration": {
        "Owner": "stelligent",
        "Repo": "dromedary",
        "Branch": "serverless",
        "OAuthToken": "XXXXXX",
      },
      "OutputArtifacts": [
        {
          "Name": "SourceOutput"
        }
      ],
      "RunOrder": 1
    }
  ]
}

 

This approach helps solve a common challenge with source code management using Lambda. Obviously no one wants to upload code through the console, so many end up using CloudFormation to manage their Lambda functions. The challenge is that the CloudFormation Lambda resource expects your code to be zipped in an S3 bucket. This means you either need to use S3 as the “source of truth” for your source code, or have a process to keep it in sync fro the real “source of truth”. By building a pipeline, you can keep your source in GitHub and use the next actions that we are about to go through to deploy the Lambda function.

Build from Commit Stage

Diagram Steps: 2,3,4

The commit stage of the pipeline consists of two actions that are implemented with Lambda invocations. The first action is responsible for resolving the application dependencies via NPM. This can be an expensive operation taking many minutes, and is needed by many downstream actions, so the dependencies are zipped up and become an output artifact of this first action. Here are the details of the action:

  • Download & Unzip – Get the source artifact from S3 and unzip into a temp directory
  • Run NPM – Run npm install  the extracted source folder
  • Zip & Upload – Zip up the source folder with its dependencies in node_modules and upload the artifact to S3

Download the input artifact is accomplished with the following code:

var artifact = null;
jobDetails.data.inputArtifacts.forEach(function (a) {
  if (a.name == artifactName && a.location.type == 'S3') {
    artifact = a;
  }
});

if (artifact != null) {
  var params = {
    Bucket: artifact.location.s3Location.bucketName,
    Key: artifact.location.s3Location.objectKey
  };
  return getS3Object(params, destDirectory);
} else {
  return Promise.reject("Unknown Source Type:" + JSON.stringify(sourceOutput));
}

Likewise, the output artifact is uploaded with the following:

var artifact = null;
jobDetails.data.outputArtifacts.forEach(function (a) {
  if (a.name == artifactName && a.location.type == 'S3') {
    artifact = a;
  }
});

if (artifact != null) {
  var params = {
    Bucket: artifact.location.s3Location.bucketName,  
    Key: artifact.location.s3Location.objectKey
  };
  return putS3Object(params, zipfile);
} else {
  return Promise.reject("Unknown Source Type:" + JSON.stringify(sourceOutput));
}

 

Diagram Steps: 5,6,7

The second action in the commit stage is responsible for acquiring the source and dependencies, processing the source code, performing static analysis, running unit tests and packaging the output artifacts. This is accomplished by an Lambda action that invokes a Gulp task on the project. This allows the details of these steps to be defined in Gulp alongside the source code and able to change at a different pace than the pipeline. Here is the CloudFormation for this action:

{
  "InputArtifacts":[
    {
      "Name": "SourceInstalledOutput"
    }
  ],
  "Name":"TestAndPackage",
  "ActionTypeId":{
    "Category":"Invoke",
    "Owner":"AWS",
    "Version":"1",
    "Provider":"Lambda"
  },
  "Configuration":{
    "FunctionName":{
      "Ref":"CodePipelineGulpLambda"
    },
    "UserParameters": "task=package&DistSiteOutput=dist/site.zip&DistLambdaOutput=dist/lambda.zip”

  },
  "OutputArtifacts": [
    {
      "Name": "DistSiteOutput"
    },
    {
      "Name": "DistLambdaOutput"
    }
  ],
  "RunOrder":2
}

Notice the UserParameters  setting defined in the resource above. CodePipeline treats it as an opaque string that is passed into the Lambda function. I chose to use a query string format to pass multiple values into the Lambda function. The task  parameter defines what gulp task to run and the DistSiteOutput  and DistLambdaOutput  parameters tell the Lambda function where to expect to find artifacts to then upload to S3.

For more details on how to implement CodePipeline actions in Lambda, check out the entire source of these functions at index.js or read the post Mocking CodePipeline with Lambda.

Test in Acceptance Stage

Diagram Steps: 8,9,10,11

The Acceptance stage is responsible for acquiring the packaged application artifacts and deploying the application to a test environment and then running a Gulp task to execute the end-to-end tests against that environment. Let’s look at the details of each of these four actions in this stage:

  • Deploy App – The Lambda for the application is updated with the code from the Commit stage as a new version. Additionally, the test  alias is moved to this new version. As you may recall from part 2 this alias is used by the test stage of the API Gateway to determine which version of the Lambda function to invoke.

LambdaVersionAliases

  • Deploy API – At this point, this is a no-op. My goal is to have this action use a Swagger file in the source code to update the API Gateway resources, methods, and integrations. This will allow these API changes to affect the API Gateway on each build, where with this current solution would require an update of the CloudFormation stack outside the pipeline to change the API Gateway.
  • Deploy Site – This action publishes all static content (HTML, CSS, JavaScript and images) to a test S3 bucket. Additionally, it published a config.json file to the bucket that the application uses to determine the endpoint for the APIs. Here’s a sample of the file that is created:
{
  "apiBaseurl":"https://rue1bmchye.execute-api.us-west-2.amazonaws.com/test/",
  "version":"20160324-231829"
}
  • End-to-end Testing – This action invokes a Gulp action to run the functional tests. Additionally, it sets and an environment variable with the endpoint URL for the application for the Gulp process to test against.

Sidebar: Continuation Token

One challenge of using Lambda for actions is the current 300 second function execution timeout limit. If you have an action that will take longer than 300 seconds (e.g., launching a CloudFormation stack) you can utilize the continuation token. A continuation token is an opaque value that you can return to CodePipeline to indicate that you are not complete with your action yet. CodePipeline will then reinvoke your action, passing in the continuation token you provided in the prior invocation.

The following code uses the UserParameters  as a maximum number of attempts and uses continuationToken  as a number of attempts. If the action needs more time, it compares the maxAttempts  with the priorAttempts  and if there are still more attempts available, it calls into CodePipeline to signal success and passes a continuation token to indicate that the action needs to be reinvoked.

var jobData = event["CodePipeline.job"].data;
var maxAttempts = parseInt(jobData.actionConfiguration.configuration.UserParameters) || 0
var priorAttempts = parseInt(jobData.continuationToken) || 0;

if(priorAttempts < maxAttempts) {
    console.log("Retrying later.");

    var params = {
        jobId: event["CodePipeline.job"].id,
        continuationToken: (priorAttempts+1).toString()
    };
    codepipeline.putJobSuccessResult(params);

}

Deploy from Production Stage

The Production stage uses the same action definitions from the Acceptance stage to deploy and test the application. The only difference is that it passes in the production S3 bucket name and Lambda ARN to deploy to.

I spent time considering how to do a Blue/Green deployment with this environment. Blue/Green deployment is an approach to reduce deployment risk by launching a duplicate environment for code changes (green environment) and then cutting over traffic from the existing (blue environment) to the new environment. This also affords a safe and quick rollback by switching traffic back to the old (blue) environment.

I looked into doing a DNS based Blue/Green using Route53 Resource Records. This would be accomplished by creating a new API Gateway and Lambda function for each job and using weighted routing to move traffic over from the old API Gateway to the new API Gateway.

I’m not convinced this level of complexity would provide much value however, because given the way Lambda manages version and API Gateway manages deployments, you can easily roll changes back very quickly by moving the Lambda version alias. One limitation though is you cannot do a canary deployment with a single API Gateway and Lambda version aliases. I’m curious what your thoughts are on this, ping me on Twitter @Stelligent with #ServerlessDelivery.

Sidebar: Gulp + CloudFormation

You’ll also notice that there is a gulpfile.js in the dromedary-serverless repo to make it easier to launch and manage the CloudFormation stack. Specifically, you can run gulp pipeline:up  to launch the stack, gulp pipeline:wait  to wait for the pipeline creation to complete and gulp pipeline:status  to see the status of the stack and its outputs. This code has been factored out into its own repo named serverless-pipeline if you’d like to add this type of integration between Gulp and CloudFormation in your own project.

Try it out!

Want to experiment with this stack in your own account? The CloudFormation templates are available for you to run with the link below. First, you’ll want to fork the dromedary repository into your GitHub account. Then you’ll need to provide the following parameters to the stack:

  • Hosted Zone – you’ll need to setup a Route53 hosted zone in your account before launching the stack for the Route53 resource records to be created in.
  • Test DNS Name – a fully qualified hostname (within the Hosted Zone you created) for the test resources (e.g.test.example.com ).
  • Production DNS Name – a fully qualified hostname (within the Hosted Zone you created) for the production resources (e.g.prod.example.com ).
  • OAuth2 Token – your OAuth2 token (see here for details)
  • User Name – your GitHub username

stack-parameters

Conclusion

In this series, we have addressed how achieve the fundamentals of continuous delivery in a serverless environment. Let’s review those fundamentals and how we addressed them:

  • Continuous – We used CodePipeline to run a series of actions against every commit to our GitHub repository.
  • Quality – We built static analysis, unit tests and end-to-end tests into our pipeline and ran them for every commit.
  • Automated – The provisioning of the pipeline and the application was done from a single CloudFormation stack
  • Reproducible – Other than creation of a Route53 Hosted Zone, there were no prerequisites to running this CloudFormation stack
  • Serverless – All the tools chosen where AWS managed services, including Lambda, API Gateway, S3, Route53 and CodePipeline. No servers were harmed in the making of this series.

Please follow us on Twitter to be informed of future articles on Serverless Delivery and other exciting topics.  Also, keep your eye out for a new book set to be released later this year by Stelligent CTO, Paul Duvall, on Continuous Delivery in AWS – which will contain a chapter on serverless delivery.

 

Resources

Serverless Delivery: Bootstrapping the Pipeline (Part 2)

In the first of this three part series on Serverless Delivery, we took a look at the high level architecture of running a continuous delivery pipeline with CodePipeline + Lambda. Our objective is to run the Dromedary application in a serverless environment with a serverless continuous delivery pipeline.

Before we can build the pipeline, we need to have the platform in place to deploy our application to. In this second part of the series we will look at what changes need to be made to a Node.js Express application to run in Lambda and the CloudFormation templates needed to create the serverless resources to host our application.

Prepare Your Application

Lambdas are defined as a function that takes in an event object containing data elements mapped to it in the API Gateway Integration Request. An application using Express in Node.js however expects its request to be initiated from an HTTP request on a socket. In order to run your Express application as a Lambda function, you’ll need some code to mediate between the two frameworks.

Although the best approach would be to have your application natively support the Lambda event, this may not always be feasible. Therefore, I have created a small piece of code to serve as a mediator and put it outside of the Dromedary application in its own module named lambda-express for others to leverage.

lambda-express

Install the module with npm install –save lambda-express  and then use it in your Express application to define the Lambda handler:

var lambdaExpress = require('lambda-express');
var express = require('express');
var app = express();

// ... initialize the app as usual ...

// create a handler function that maps lambda inputs to express
exports.handler = lambdaExpress.appHandler(app);

In the dromedary application, this is available in a separate index.js file. You’ll also notice in the dromedary application, that it passes a callback function rather than the express app to the appHandler function. This allows it to use information on the event to configure the application, in this case via environment variables:

exports.handler = lambdaExpress.appHandler(function(event,context) {
process.env.DROMEDARY_DDB_TABLE_NAME = event.ddbTableName;

var app = require('./app.js');
return app;
});

You now have an Express application that will be able to respond to Lambda events that are generated from API Gateway. Now let’s look at what resources need to be defined in your CloudFormation templates. For the dromedary application, these templates are defined in a separate repository named dromedary-serverless in the pipeline/cfn directory.

Define Website Resources

Sample – site.json

Buckets need to be defined for test and production stages of the pipeline to host the static content of the application. This includes the HTML, CSS, images and any JavaScript that will run in the browser. Each bucket will need a resource like what you see below.

"TestBucket" : {
  "Type" : "AWS::S3::Bucket",
  "Properties" : {
    "AccessControl" : "PublicRead",
    "BucketName" : “www-test.mysite.com”,
    "WebsiteConfiguration" : {
      "IndexDocument" : "index.html"
    }
  }
}

Here are the important pieces to pay attention to:

  • AccessControl – set to to PublicRead  assuming this is a public website.
  • WebsiteConfiguration – add an IndexDocument  entry to define the home page for the bucket.
  • BucketName – needs to match exactly the Name  for the Route53 ResourceRecord you create. For example, if I’m going to setup a DNS record for www-test.mysite.com , then the bucket name should also be www-test.mysite.com .

We will also want Route53 resource records for each of the test and production buckets. Here is a sample record:

"TestSiteRecord": {
  "Type": "AWS::Route53::RecordSetGroup",
  "Properties": {
    "HostedZoneId": “Z00ABC123DEF”,
    "RecordSets": [{
      "Name": “www-test.mysite.com.”,
      "Type": "A",
      "AliasTarget": {
        "HostedZoneId": “Z3BJ6K6RIION7M”,
        "DNSName": “s3-website-us-west-2.amazonaws.com"
      }
    }]
  }
}

Make sure that your record does the following:

  • Name – must be the same as the bucket name above with a period at the end.
  • HostedZoneId – should be specific to your account and for the zone you are hosting (mysite.com in this example).
  • AliasTarget – will be reference the zone id and endpoint for S3 in the region you created the bucket. The zone ids and endpoints can be found in the AWS General Reference Guide.

Declare Lambda Functions

Sample – app.json

Lambda functions will need to be declared for test and production stages of the pipeline to serve the Express application. Each function will only be stubbed out in the CloudFormation template so the API Gateway resources can reference it. Each execution of a CodePipeline job will deploy the latest version of the code as a new version of the function. Here is a sample declaration of the Lambda function in CloudFormation:

"TestAppLambda": {
  "Type" : "AWS::Lambda::Function",
  "Properties" : {
    "Code" : {
      "ZipFile": { "Fn::Join": ["n", [
        "exports.handler = function(event, context) {",
        " context.fail(new Error(500));",
        "};"
      ]]}
    },
    "Description" : "serverless application",
    "Handler" : "index.handler",
    "MemorySize" : 384,
    "Timeout" : 10,
    "Role" : {“Ref”: “TestAppLambdaTrustRole”},
    "Runtime" : "nodejs"
  }
}

Notice the following about the Lambda resource:

  • ZipFile – the default implementation of the function is provided inline. Notice it just returns a 500 error. This will be replaced by real code when CodePipeline runs.
  • MemorySize – this is the only control you have over the system resources allocated to your function. CPU performance is determined by the amount of memory you allocate, so if you need more CPU, increase this number. Your cost is directly related to this number as is the duration of each invocation. There is a sweet spot you need to find where you get the shortest durations for the system resources.
  • Timeout – max time (in seconds) for a given invocation of the function to run before it is forcibly terminated. The maximum value for this is 300 seconds.
  • Role – reference the ARN of the IAM role that you want to assign to your function when it runs. You’ll want to have “Principal”:{“Service”:[“lambda.amazonaws.com”]} in the AssumePolicyDocument to grant the Lambda service access to the sts:AssumeRole action. You’ll also want to include in the policy access to CloudWatch Logs with “Action”: [“logs:CreateLogGroup”,”logs:CreateLogStream”,”logs:PutLogEvents”]

Define API Gateway and Stages

Sample – api-gateway.json

Our Lambda function requires something to receive HTTP requests and deliver them as Lambda events. Fortunately, the AWS API Gateway is a perfect solution for this need. A single API Gateway definition with two stages, one for test and one for production will be defined to provide the public access to your Lambda function defined above. Unfortunately, CloudFormation does not have support yet for API Gateway. However, Andrew Templeton has created a set of custom resources that do a great job of filling the gap. For each package, you will need to create a Lambda function in your CloudFormation template, for example:

"ApiGatewayRestApi": {
  "Type" : "AWS::Lambda::Function",
  "Properties" : {
    "Code" : {
      "S3Bucket": “dromedary-serverless-templates”,
      "S3Key": "cfn-api-gateway-restapi.zip"
    },
    "Description" : "Custom CFN Lambda",
    "Handler" : "index.handler",
    "MemorySize" : 128,
    "Timeout" : 30,
    "Role" : { "Ref": "ApiGatewayCfnLambdaRole" },
    "Runtime" : "nodejs"
  }
}

 

Make sure the role that you create and reference from the Lambda above contains policy statements to give it access to apigateway:*  actions, as well as granting the custom resource access to PassRole  the role defined for API Integration.

{
  "Effect": "Allow",
  "Resource": [
    { "Fn::GetAtt": [ "ApiIntegrationCredentialsRole", "Arn" ] }
  ],
  "Action": [
    "iam:PassRole"
  ]
}

 

Defining the API Gateway above consists of the following six resources. For each one, I will highlight only the important properties to be aware of as well as a picture from the console of what the CloudFormation resource creates:

  1. cfn-api-gateway-restapi – this is the top level API definition.
    apigw-api
    • Name – although not required, you ought to provide a unique name for the API
  2. cfn-api-gateway-resource – a resource (or path) for the API. In the reference application, I’ve created just one root resource that is a wildcard and captures all sub paths.  The sub path is then passed into lambda-express as a parameter and mapped into a path that Express can handle.
     apigw-resource
    • PathPart – define a specific path of your API, or {subpath}  to capture all paths as a variable named subpath
    • ParentId – This must reference the RootResourceId from the restapi resource
      "ParentId": { "Fn::GetAtt": [ "RestApi", "RootResourceId" ] }
  3. cfn-api-gateway-method – defines the contract for a request to an HTTP method (e.g., GET or POST) on the path created above.
    apigw-method
    • HttpMethod – the method to support (GET)
    • RequestParameters – a map of parameters on the request to expect and pass down to the integration
      "RequestParameters": {
        "method.request.path.subpath": true
      }
  4. cfn-api-gateway-method-response – defines the contract for the response from an HTTP method defined above.
     apigw-method-response
    • HttpMethod – the method to support
    • StatusCode – the code to return for this response (e.g., 200)
    • ResponseParameters – a map of the headers to include in the response
      "ResponseParameters": {
        "method.response.header.Access-Control-Allow-Origin": true,
        "method.response.header.Access-Control-Allow-Methods": true,
        "method.response.header.Content-Type": true
      }
  5. cfn-api-gateway-integration – defines where to send the request for a given method defined above.
    apigw-integration
    • Type – for Lambda function integration, choose AWS
    • IntegrationHttpMethod – for Lambda function integration, choose POST
    • Uri – the AWS service URI to integrate with. For Lambda use the example below. Notice that the function name and version are not in the URI, but rather there are variables in their place. This way we can allow the different stages (test and production) to control which Lambda function and which version of that function to call:
      arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-2::function:${stageVariables.AppFunctionName}:${stageVariables.AppVersion}/invocations
    • Credentials – The role to run as when invoking the Lambda function
    • RequestParameters – The mapping of parameters from the request to integration request parameters
      "RequestParameters": {
        "integration.request.path.subpath": "method.request.path.subpath"
      }
    • RequestTemplates – The template for the JSON to pass to the Lambda function. This template captures all the context information from API Gateway that lambda-express will need to create the request that Express understands:
      "RequestTemplates": {
        "application/json": {
          "Fn::Join": ["n",[
            "{",
            " "stage": "$context.stage",",
            " "request-id": "$context.requestId",",
            " "api-id": "$context.apiId",",
            " "resource-path": "$context.resourcePath",",
            " "resource-id": "$context.resourceId",",
            " "http-method": "$context.httpMethod",",
            " "source-ip": "$context.identity.sourceIp",",
            " "user-agent": "$context.identity.userAgent",",
            " "account-id": "$context.identity.accountId",",
            " "api-key": "$context.identity.apiKey",",
            " "caller": "$context.identity.caller",",
            " "user": "$context.identity.user",",
            " "user-arn": "$context.identity.userArn",",
            " "queryString": "$input.params().querystring",",
            " "headers": "$input.params().header",",
            " "pathParams": "$input.params().path",",
            "}"
          ] ]
        }
      }
  6. cfn-api-gateway-integration-response – defines how to send the response back to the API client
     apigw-integration-response
    • ResponseParameters – a mapping from the integration response to the method response declared above. Notice the CORS headers that are necessary since the hostname for the API Gateway is different from the hostname provided in the Route53 resource record for the S3 bucket. Without these, the browser will deny AJAX request from the site to these APIs. You can read more about CORS in the API Gateway Developer Guide. Also notice that the response Content-Type is pulled from the contentType attribute in the JSON object returned from the Lambda function:
      "ResponseParameters": {
        "method.response.header.Access-Control-Allow-Origin": "'*'",
        "method.response.header.Access-Control-Allow-Methods": "'GET, OPTIONS'",
        "method.response.header.Content-Type": "integration.response.body.contentType"
      }
    • ResponseTemplates – a template of how to create the response from the Lambda invocation. In the reference application, the Lambda function returns a JSON object with a payload attribute containing a Base64 encoding of the response payload:
      "ResponseTemplates": {
        "application/json": "$util.base64Decode( $input.path('$.payload') )"
      }

       

Additionally, there are two deployment resources created, one for test and one for production. Here is an example of one:

  • cfn-api-gateway-deployment – a deployment has a name that will be the prefix for all resources defined.
     apigw-deployment
    • StageName – “test” or “prod”
    • Variables – a list of variables for the stage. This is where the function name and version are defined for the integration URI defined above:
      "Variables": {
        "AppFunctionName": "MyFunctionName”,
        "AppVersion": "prod"
      }

       

Deploy via Swagger

I would prefer to replace most of the above API Gateway CloudFormation with a Swagger file in the application source repository and have the pipeline use the import tool to create API Gateway deployments from the Swagger. There are a few challenges with this approach. First, the creation of the Swagger requires including the AWS extensions which has a bit of a learning curve. This challenge is made easier by the fact that you can create the API Gateway via the console and then export Swagger. The other challenge is that the import tool is a Java based application that requires Maven to run. This may be difficult to get working in a Lambda invocation from CodePipeline especially given the 300 second timeout.  I will however be spending some time researching this option and will blog about the results.

Stay Tuned!

Now that we have all the resources in place to deploy our application to, we can build a serverless continuous delivery pipeline with CodePipeline and Lambda. Next week we conclude this series with the third and final part looking at the details of the CloudFormation template for the pipeline and each stage of the pipeline as well as the Lambda functions that support them. Be sure to check it out!

Resources

Serverless Delivery: Architecture (Part 1)

If your application tech stack doesn’t need servers, why should your continuous delivery pipeline? Serverless applications deserve serverless delivery!

The software development discipline of continuous delivery has had a tremendous impact on decreasing the cost and risk of delivering changes while simultaneously increasing code quality by ensuring that software systems are always in a releasable state. However, when applying the tools and techniques that exist for this practice to serverless application frameworks and platforms, sometimes existing toolsets do not align well with these new approaches. This post is the first in a three-part series that looks at how to implement the same fundamental tenets of continuous delivery while utilizing tools and techniques that complement the serverless architecture in Amazon Web Services (AWS).

Here are the requirements of the serverless delivery pipeline:

  • Continuous – The pipeline must be capable of taking any commit on master that passes all test cases to production
  • Quality – The pipeline must include unit testing, static code analysis and functional testing of the application
  • Automated – The provisioning of the pipeline and the application must be done from a single CloudFormation command
  • Reproducible – The CloudFormation template should be able to run on a new AWS account with no additional setup other than creation of a Route53 Hosted Zone
  • Serverless – All layers of the application must run on platforms that meet the definition of serverless as described in the next section

What is Serverless?

What exactly is a serverless platform? Obviously, there is still hardware at some layer in the stack to host the application. However, what Amazon has provided with Lambda is a platform where developers no longer need to think about the following:

  • Operating System  – no need to select, secure, configure, administer or patch the OS
  • Servers– no cost risk of over-provisioning and no performance risk of under-provisioning
  • Capacity – no need to monitor utilization and scale capacity based on load
  • High Availability– compute resources are available across multiple AZs

In summary, a serverless platform is one in which an application can be deployed on top of without having to provision or administer any of the resources within the platform. Just show up with some code and the platform handles all the ‘ilities’.

app-overview

The diagram above highlights the components that are used in this serverless application. Let’s look at each one individually:

  • Node.js + Express – In the demo application for this blog series, we will be deploying a Node.js JavaScript application that leverages the Express framework into Lambda. There is a small bit of “glue” code that we will highlight later in the series to adapt the Lambda contract to the Express contract.
  • Lambda – This is where your application logic runs. You deploy your code here but do not need to specify the number of servers or size of the servers to run the code on. You only pay for the number of requests and amount of time those requests take to execute.
  • API Gateway – The API Gateway exposes your Lambda function at an HTTP endpoint. It provides capabilities such as authorization, policy enforcement, rate limiting and data transformation as a service that is entirely managed by Amazon.
  • DynamoDB – Dynamic data is stored in a DynamoDB table. DynamoDB is a NoSQL datastore that has both infinitely scalable storage capacity and throughput capacity which is entirely managed by Amazon.
  • S3 – All static content including HTML, CSS, images and client-side JavaScript is stored in an S3 bucket and served as a static website directly from S3.

The diagram below compares the pricing for running a Node.js application with Lambda and API Gateway versus a pair of EC2 instances and an ELB. Notice that for the m4.large, the break even is around two million requests per day. It is important to mention that 98.8% of the cost of the serverless deployment is the API Gateway. The cost of running the application out of Lambda is insignificant relative to the costs in using API Gateway.

serverless-app-cost

This cost analysis shows that applications/environments with low transaction volume can realize cost savings by running on Lambda + API Gateway, but the cost of API Gateway will become cost prohibitive at higher scale.

What is Serverless Delivery?

Serverless delivery is just the application of serverless platforms to achieve continuous delivery fundamentals. Specifically, a serverless delivery pipeline does not include tools such as Jenkins or resources such as EC2, Autoscaling Groups, and ELBs.

pipeline

The diagram above shows the technology used to accomplish serverless delivery for the sample application. Let’s look at what each component provides:

  • AWS CodePipeline – Orchestrates various Lambda tasks to move code that was checked into GitHub forward towards production.
  • AWS S3 Artifact Bucket – Each action in the pipeline can create a new artifact. The artifact becomes an output from the action in the pipeline and is stored in an S3 bucket to become an input for the next action.
  • AWS Lambda – You create Lambda functions to do the work of individual actions in the pipeline. For example, running a gulp task on a repository is handled by a Lambda function.
  • NPM and Gulp – NPM is used for resolving all the dependencies of a given repository. Gulp is used for defining the tasks of the repository, such as running unit tests and packaging artifacts.
  • Testing toolsJSHint is used for performing static analysis of the code, Mocha is a unit test framework for JavaScript and Chai is a BDD assertion library for describing the unit tests.
  • AWS CloudFormation – CFN templates are used to create and update all these resources in a serverless stack.

Serverless delivery for traditional architectures?

Although this serverless delivery architecture could be applied to more traditional application architectures (e.g., a Java application on EC2 and ELB resources) the challenge might be having pipeline actions complete within the 300 second Lambda maximum. For example, running Maven phases within a single Lambda function invocation, including the resolving of dependencies, compilation, unit testing and packaging would likely be difficult. There may be opportunities to split up the goals into multiple invocations and persist state to S3, but that is beyond the scope of this series.

Pricing

The pricing model for Lambda is favorable for applications that have an idle time and the cost grows linearly with the number of executions. The diagram below compares the pricing for running the pipeline with Lambda and CodePipeline against a Jenkins server running on an EC2 instance. For best performance, the Jenkins server ought to run on an m4.large instance, but just to highlight the savings, m3.medium and t2.micro instances were evaluated as well. Notice that for the m4.large, the break even happens after you are doing over 600 builds per day and even with a t2.micro, the break even doesn’t happen until well over 100 builds per day.

serverless-delivery-cost
In conclusion, running a continuous delivery pipeline with CodePipeline + Lambda is very attractive based on the cost efficiency of utility pricing, the simplicity of using a managed services environment, and the tech stack parity of using Node.js for both the application and the pipeline.

Stay Tuned!

Next week we will dive into part two of this series, looking at what changes need to be made for an Express application to run in Lambda and the CloudFormation templates needed to create a serverless delivery pipeline. Finally, we will conclude with part three going into the details of each stage of the pipeline and the Lambda functions that support them.

Resources

Create a Pipeline using the AWS CodePipeline Console

In this demonstration, you’ll perform a simple walkthrough of a two-stage pipeline using the CodePipeline console. This is based on AWS’ Simple Pipeline Walkthrough. A screencast is provided below.

Using the AWS console is often the best way to get familiar with the concepts around any new AWS service. The console is also a good way to view the status of resource configuration and attributes. In posts on Provisioning AWS CodePipeline with CloudFormation and Mocking AWS CodePipeline pipelines with Lambda, you became familiar with how to automate the provisioning of pipelines in CodePipeline using CloudFormation – along with many other AWS resources. In this post, you’ll take a step back and go over the steps of manually creating a simple pipeline using the AWS console.

Create an application and deployment in CodeDeploy

To create an application and deployment, go to the AWS Console and launch the CodeDeploy service. Choose Sample Deployment and click Next Step.

create-pipeline-step-4

On the next page, choose Amazon Linux as the Operating System, then choose your EC2 Key Pair from the drop down and you’ll want to keep the Name and CodeDeployDemo defaults for Tag Key and Value. Finally, click Launch Instances. You’ll need to wait several minutes while CodeDeploy launches a CloudFormation stack that provisions three EC2 instances and installs the CodeDeploy agent on each of these instances. Once the instances have been launched, click Next step.

create-pipeline-step-5.jpg

Enter an Application Name (or just use the default name) and click Next step. Click Next step from the Revision page. From the Deployment Group page, accept the defaults and click Next step. From the Service Role page, select an IAM role with access to CodeDeploy resources (this should have been previously created when you launched the EC2 instances for CodeDeploy). Click Next step. Review the Deployment Configuration and click Next step. From the Review page, click Deploy Now.

create-pipeline-step-11

Access the application deployed by CodeDeploy

By going through these steps, you will launch a deployment of a simple web application across three EC2 instances. Check the CodeDeploy console for deployment status. Once everything is successful, choose, click View All Instances and click on the one of the links in the in Instance ID column to bring up the EC2 console. From the details pane, copy the Public IP and paste it into your browser. You should see a page that says something similar to “Congratulations. This application was deployed using AWS CodeDeploy.”.

create-codedeploy-step-1

Upload an application distribution to S3

Next, you will download the CodePipeline/CodeDeploy application distribution by going to the link to the CodePipeline/CodeDeploy distribution provided in the Resources section in this post. Upload the zip file to an S3 bucket in your AWS account and make note of your bucket name and key as you will be using it when creating a pipeline in CodePipeline. Make sure you enable versioning on the S3 bucket by selecting Properties for the bucket, clicking on Versioning and then Enable Versioning.

Create a Pipeline

Once this is a complete, go to the CodePipeline console and click Create pipeline. Enter your pipeline name and click Next step. Choose Amazon S3 as the Source provider followed by the S3 Location of the location (e.g. s3://stelligent-tmp/aws-codepipeline-s3-aws-codedeploy_linux.zip where stelligent-tmp is the bucket name and aws-codepipeline-s3-aws-codedeploy_linux.zip is the key) where you uploaded the zip file located in GitHub. Click Next step. Choose No Build as the Build provider and click Next step. From the Beta page, choose AWS CodeDeploy as the Deployment provider and enter the Application name and Deployment group that you defined when create the sample CodeDeploy deployment. Click Next step.

create-pipeline-step-13
Choose AWS-CodePipeline-Service as your IAM Role name (or another role that has proper access to the CodePipeline services) on the AWS Service Role page and click Next step. Review your pipeline and click Create pipeline.

Access the application deployed by CodeDeploy and orchestrated by CodePipeline

From the CodePipeline console you can monitor the stages and actions for this simple two-stage pipeline. Once it successfully completes, refresh the page where you copied the URL when setting up CodePipeline. You should see a message similar to “Congratulations! You have successfully created a pipeline that retrieved this source application from an Amazon S3 bucket and deployed it to three Amazon EC2 instances using AWS CodeDeploy.” You’ve successfully orchestrated a CodeDeploy deployment that deploy a simple application from an S3 bucket.

create-pipeline-step-17

Delete your Pipeline

From the CodePipeline console, select the pipeline you created and click Edit. Then, click Delete and enter the name of the pipeline you’d like to delete. You can delete your CodeDeploy application and CloudFormation stacks as well.

Resources

Running AWS Lambda Functions in AWS CodePipeline using CloudFormation

Recently, AWS announced that they’ve added support for triggering AWS Lambda functions into AWS CodePipeline – AWS’ Continuous Delivery service. They also provided some great step-by-step documentation to describe the process for configuring a new stage in CodePipeline to run a Lambda function. In this article, I’ll describe how I codified the provisioning of all of the AWS resources in the documentation using CloudFormation.

aws_code_pipeline_lambda

This announcement is really big news as it opens up a whole realm of possibilities about what can be run from CodePipeline. Now, you can run event-driven functions any time you want from your pipelines. With this addition, CodePipeline added a new Invoke action category that adds to the list of other actions such as Build, Deploy, Test and Source.

NOTE: All of the CloudFormation examples in this article are defined in the codepipeline-lambda.json file.

tl;dr

If you’d rather not read the detailed explanation of the resources and code snippets of this solution, just click on the CloudFormation Launch Stack button below to automatically provision the AWS resources described herein. You will be charged for your AWS usage. 
Launch Stack

CloudFormation

I went through the 20+ pages of instructions which were easy to follow but, as I often do when going through this kind of documentation, I thought about how I’d make it easier for me and others to run it again without copying/pasting, clicking multiple buttons and so on. In other words, I’m lazy and don’t enjoy repeatedly going over the same thing again and again and I figured this would be something I’d (and others) like to use often in the future. Of course, this leads me to writing a template in CloudFormation since I can define everything in code and type a single command or click a button to reliably and repeatedly provision all the necessary resources to run Invoke actions within a Lambda stage in CodePipeline.

There are six core services that compose this infrastructure architecture. They are CloudFormation, CodePipeline, Lambda, IAM, EC2 and CodeDeploy.

To launch the infrastructure stacks that make up this solution, type the following from the command line. The command will only work if you’ve installed the AWS CLI.

Command for launching CodePipeline Lambda stacks

aws cloudformation create-stack 
--stack-name CodePipelineLambdaStack  
--template-body https://raw.githubusercontent.com/stelligent/stelligent_commons/master/cloudformation/codepipeline-lambda.json 
--region us-east-1 
--disable-rollback --capabilities="CAPABILITY_IAM" 
--parameters ParameterKey=KeyName,ParameterValue=YOUREC2KEYPAIRNAME

EC2

From my CloudFormation template, I launched a single EC2 instance that installed a CodeDeploy agent onto it. I used the sample provided by AWS at http://s3.amazonaws.com/aws-codedeploy-us-east-1/templates/latest/CodeDeploy_SampleCF_Template.json and added one small modification to return the PublicIp of the EC2 instance after it’s launched as a CloudFormation Output. Because of this modification, I created a new template based on AWS’ sample.
CloudFormation JSON to define EC2 instance used by CodeDeploy

    "CodeDeployEC2InstancesStack":{
      "Type":"AWS::CloudFormation::Stack",
      "Properties":{
        "TemplateURL":"https://s3.amazonaws.com/stelligent-training-public/public/codedeploy/codedeploy-ec2.json",
        "TimeoutInMinutes":"60",
        "Parameters":{
          "TagValue":{
            "Ref":"AWS::StackName"
          },
          "KeyPairName":{
            "Ref":"KeyName"
          }
        }
      }
    },

When the stack is complete, you’ll see that one EC2 instance has been launched and automatically tagged with the name you entered when naming your CloudFormation stack. This name is used to run CodeDeploy operations on instance(s) with this tag.
codepipeline_lambda_ec2

CodeDeploy

AWS CodeDeploy automates code deployments to any instance. Previously, I had automated the steps of the Simple Pipeline Walkthrough which included the provisioning of AWS CodeDeploy resources as well so I used this CloudFormation template as a starting point. I uploaded the sample Linux app provided by CodePipeline in the walkthrough to Amazon S3 and used S3 as the Source action in the Source stage in my pipeline in CodePipeline. Below, you see a snippet of defining the CodeDeploy stack from the codepipeline-lambda.json. The nested stack defined in the TemplateURL property defines the CodeDeploy application and the deployment group.

CloudFormation JSON to define EC2 instance used by CodeDeploy

    "CodeDeploySimpleStack":{
      "Type":"AWS::CloudFormation::Stack",
      "DependsOn":"CodeDeployEC2InstancesStack",
      "Properties":{
        "TemplateURL":"https://s3.amazonaws.com/stelligent-training-public/public/codedeploy/codedeploy-deployment.json",
        "TimeoutInMinutes":"60",
        "Parameters":{
          "TagValue":{
            "Ref":"AWS::StackName"
          },
          "RoleArn":{
            "Fn::GetAtt":[
              "CodeDeployEC2InstancesStack",
              "Outputs.CodeDeployTrustRoleARN"
            ]
          },
          "Bucket":{
            "Ref":"S3Bucket"
          },
          "Key":{
            "Ref":"S3Key"
          }
        }
      }
    },

The screenshot below is that of a CodeDeploy deployment that was generated from the CloudFormation stack launch.
codepipeline_lambda_codedeploy

The CodeDeploy provisioning of this is described in more detail in my article on this topic: Automating AWS CodeDeploy Provisioning in CloudFormation.

CodePipeline

I took a CodePipeline example that I’d written in CloudFormation that defines a simple three-stage pipeline (based on the Simple Pipeline Walkthrough)  and added a new stage in the CloudFormation Resource block to invoke the Lambda function. If I were manually adding this stage, I’d go to my specific pipeline in AWS CodePipeline, click add Stage and then add an action to the stage. Below, you see of a screenshot of what you’d do if you were manually defining this configuration within an AWS CodePipeline action. This is also what got generated from the CloudFormation stack.

codepipeline_lambda_stage

AWS::CodePipeline::Pipeline

At the beginning of the snippet below, you see the use of the AWS::CodePipeline::Pipeline CloudFormation resource type. It has dependencies on the CodeDeploySimpleStack and CodePipelineLambdaTest resources. One of the reasons for this is that there needs to be an EC2 instance type defined already so that I can get access to the PublicIp that I use to run a Lambda function later when verifying the application is up and running. The other is that we need to set the FunctionName property of the Configuration of the Lambda stage in CodePipeline. This function name is generated by the AWS::Lambda::Function resource type that I’ll describe later. By using this approach, you don’t need to know the name of the Lambda function when defining the CloudFormation template.
CloudFormation JSON to define IAM Role for Lambda function execution

    "GenericPipeline":{
      "Type":"AWS::CodePipeline::Pipeline",
      "DependsOn":[
        "CodeDeploySimpleStack",
        "CodePipelineLambdaTest"
      ],
      "Properties":{
        "DisableInboundStageTransitions":[
          {
            "Reason":"Demonstration",
            "StageName":"Production"
          }
        ],
        "RoleArn":{
          "Fn::Join":[
            "",
            [
              "arn:aws:iam::",
              {
                "Ref":"AWS::AccountId"
              },
              ":role/AWS-CodePipeline-Service"
            ]
          ]
        },
        "Stages":[
...
          {
            "Name":"LambdaStage",
            "Actions":[
              {
                "InputArtifacts":[

                ],
                "Name":"MyLambdaAction",
                "ActionTypeId":{
                  "Category":"Invoke",
                  "Owner":"AWS",
                  "Version":"1",
                  "Provider":"Lambda"
                },
                "OutputArtifacts":[

                ],
                "Configuration":{
                  "FunctionName":{
                    "Ref":"CodePipelineLambdaTest"
                  },
                  "UserParameters":{
                    "Fn::Join":[
                      "",
                      [
                        "http://",
                        {
                          "Fn::GetAtt":[
                            "CodeDeployEC2InstancesStack",
                            "Outputs.PublicIp"
                          ]
                        }
                      ]
                    ]
                  }
                },
                "RunOrder":1
              }
            ]
          },...

Lambda

AWS Lambda lets you run event-based functions without provisioning or managing servers. That said, there’s still a decent amount of configuration you’ll need to define in running your Lambda functions. In the example provided by AWS, the Lambda function tests whether it can access a website without receiving an error. If it succeeds, the CodePipeline action and stage succeed, turn to green, and it automatically transitions to the next stage or completes the pipeline. If it fails, that pipeline instance fails, turns red, and ceases any further actions from occurring. It’s a very typical test you’d run to be sure your application was successfully deployed. In the example, AWS has you manually enter the URL for the application. Since this requires manual intervention, I needed to figure out a way to get this URL dynamically. I did this by setting the PublicIp of the EC2 instance that was launched earlier in the stack as an Output of the nested stack. Then I used this PublicIp as an input to the UserParameters property of the Invoke action within the Lambda stage that I defined in CloudFormation for my CodePipeline pipeline.

Once the function has been generated by the stack, you’ll be able to go to a list of Lambda functions in your AWS Console and see the function that was created from the stack.

codepipeline_lambda_function

AWS::IAM::Role

In the CloudFormation code snippet you see below, I’m defining an IAM role that’s capable of calling Lambda functions.
CloudFormation JSON to define IAM Role for Lambda function execution

    "CodePipelineLambdaRole":{
      "Type":"AWS::IAM::Role",
      "Properties":{
        "AssumeRolePolicyDocument":{
          "Version":"2012-10-17",
          "Statement":[
            {
              "Effect":"Allow",
              "Principal":{
                "Service":[
                  "lambda.amazonaws.com"
                ]
              },
              "Action":[
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Path":"/"
      }
    },

AWS::IAM::Policy

The code snippet below depends on the creation of the IAM role I showed in the example above. The IAM policy that’s attached to the IAM role provides access to the AWS logs and the CodePipeline results so that it can signal success or failure to the CodePipeline action that I defined earlier.
CloudFormation JSON to define IAM Policy for IAM Role for Lambda function execution

    "LambdaCodePipelineExecutionPolicy":{
      "DependsOn":[
        "CodePipelineLambdaRole"
      ],
      "Type":"AWS::IAM::Policy",
      "Properties":{
        "PolicyName":"LambdaRolePolicy",
        "Roles":[
          {
            "Ref":"CodePipelineLambdaRole"
          }
        ],
        "PolicyDocument":{
          "Version":"2012-10-17",
          "Statement":[
            {
              "Effect":"Allow",
              "Action":[
                "logs:*"
              ],
              "Resource":[
                "arn:aws:logs:*:*:*"
              ]
            },
            {
              "Effect":"Allow",
              "Action":[
                "codepipeline:PutJobSuccessResult",
                "codepipeline:PutJobFailureResult"
              ],
              "Resource":[
                "*"
              ]
            }
          ]
        }
      }
    },

AWS::Lambda::Function

In the code snippet below, you see how I’m defining the Lambda function in CloudFormation. There are several things to point out here. I uploaded some JavaScript (Node.js) code to S3 with the name Archive.zip into a bucket specified by the S3Bucket parameter that I set when I launched the CloudFormation stack. This S3 bucket needs to have S3 Versioning enabled on it. Moreover, the Archive.zip file needs to have the .js file used by Lambda in the root of the Archive.zip. Keep in mind that I can call the .zip file whatever I want, but once I name the file and upload it then my CloudFormation template needs to refer to the correct name of the file.

Also, you see that I’ve defined a Handler named validateurl.handler. This means that the JavaScript file in the Archive.zip that hosts the file(s) that Lambda runs must be named validateurl.js. If I want to use a different name, I must change both the JavaScript filename and the CloudFormation template that references it.

CloudFormation JSON to define Lambda function execution

    "CodePipelineLambdaTest":{
      "Type":"AWS::Lambda::Function",
      "DependsOn":[
        "CodePipelineLambdaRole",
        "LambdaCodePipelineExecutionPolicy"
      ],
      "Properties":{
        "Code":{
          "S3Bucket":{
            "Ref":"S3Bucket"
          },
          "S3Key":"Archive.zip"
        },
        "Role":{
          "Fn::GetAtt":[
            "CodePipelineLambdaRole",
            "Arn"
          ]
        },
        "Description":"Validate a website URL",
        "Timeout":20,
        "Handler":"validateurl.handler",
        "Runtime":"nodejs",
        "MemorySize":128
      }
    },

Lambda Test Function

With all of this configuration to get something to run, sometimes it’s easy to overlook that we’re actually executing something useful and not just configuring the support infrastructure. The snippet below is the actual test that gets run as part of the Lambda action in the Lambda stage that I defined in the CloudFormation template for CodePipeline. This code is taken directly from the Integrate AWS Lambda Functions into Pipelines in AWS CodePipeline instructions from AWS. This JavaScript code verifies that it can access the supplied website URL of the deployed application. If it fails, it signals for CodePipeline to cease any further actions in the pipeline.

JavaScript to test access to a website

var assert = require('assert');
var AWS = require('aws-sdk');
var http = require('http');

exports.handler = function(event, context) {

    var codepipeline = new AWS.CodePipeline();

    // Retrieve the Job ID from the Lambda action
    var jobId = event["CodePipeline.job"].id;

    // Retrieve the value of UserParameters from the Lambda action configuration in AWS CodePipeline, in this case a URL which will be
    // health checked by this function.
    var url = event["CodePipeline.job"].data.actionConfiguration.configuration.UserParameters;

    // Notify AWS CodePipeline of a successful job
    var putJobSuccess = function(message) {
        var params = {
            jobId: jobId
        };
        codepipeline.putJobSuccessResult(params, function(err, data) {
            if(err) {
                context.fail(err);
            } else {
                context.succeed(message);
            }
        });
    };

    // Notify AWS CodePipeline of a failed job
    var putJobFailure = function(message) {
        var params = {
            jobId: jobId,
            failureDetails: {
                message: JSON.stringify(message),
                type: 'JobFailed',
                externalExecutionId: context.invokeid
            }
        };
        codepipeline.putJobFailureResult(params, function(err, data) {
            context.fail(message);
        });
    };

    // Validate the URL passed in UserParameters
    if(!url || url.indexOf('http://') === -1) {
        putJobFailure('The UserParameters field must contain a valid URL address to test, including http:// or https://');
        return;
    }

    // Helper function to make a HTTP GET request to the page.
    // The helper will test the response and succeed or fail the job accordingly
    var getPage = function(url, callback) {
        var pageObject = {
            body: '',
            statusCode: 0,
            contains: function(search) {
                return this.body.indexOf(search) > -1;
            }
        };
        http.get(url, function(response) {
            pageObject.body = '';
            pageObject.statusCode = response.statusCode;

            response.on('data', function (chunk) {
                pageObject.body += chunk;
            });

            response.on('end', function () {
                callback(pageObject);
            });

            response.resume();
        }).on('error', function(error) {
            // Fail the job if our request failed
            putJobFailure(error);
        });
    };

    getPage(url, function(returnedPage) {
        try {
            // Check if the HTTP response has a 200 status
            assert(returnedPage.statusCode === 200);
            // Check if the page contains the text "Congratulations"
            // You can change this to check for different text, or add other tests as required
            assert(returnedPage.contains('Congratulations'));

            // Succeed the job
            putJobSuccess("Tests passed.");
        } catch (ex) {
            // If any of the assertions failed then fail the job
            putJobFailure(ex);
        }
    });
};

Post-Commit Git Hook for Archiving and Uploading to S3

I’m in the process of figuring out how to add a post-commit hook that moves files committed to a specific directory in a Git repository, zip up the necessary artifacts and upload to a pre-defined directory in S3 so that I can remove this manual activity as well.

Summary

By adding the ability to invoke Lambda functions directly from CodePipeline, AWS has opened a whole new world of what can be orchestrated into our software delivery processes in AWS. You learned how to automate the provisioning of not just the Lambda configuration, but dependent AWS resources including the automated provisioning of your pipelines in AWS CodePipeline. If you have any questions, reach out to us on Twitter @stelligent or @paulduvall.

Resources

Automating AWS CodeDeploy Provisioning in CloudFormation

Over the past few weeks, I’ve been describing how I’m automating the provisioning of several of the AWS Code Services including CodePipeline and Custom CodePipeline Actions. This time, I’m describing a way of provisioning AWS CodeDeploy in CloudFormation. For now, I’m doing the automation against a generic application provided by AWS. I’ll apply it to our Dromedary demo application later and publish it in a future post.

DeploymentManagement_CodeDeploy

   About AWS CodeDeploy

As AWS describes: “AWS CodeDeploy is a service that automates code deployments to any instance, including Amazon EC2 instances and instances running on-premises. AWS CodeDeploy makes it easier for you to rapidly release new features, helps you avoid downtime during application deployment, and handles the complexity of updating your applications. You can use AWS CodeDeploy to automate software deployments, eliminating the need for error-prone manual operations, and the service scales with your infrastructure so you can easily deploy to one instance or thousands.” Below, I describe the core components that make up CodeDeploy.

  • AppSpec – The appspec.yml must be in the root directory of an AWS CodeDeploy deployment bundle. Here’s an example appspec.yml file from AWS.
  • Application – The application is the container for the deployment and deployment configuration. The application uses the appspec.yml to define how the lifecycle events behave.
  • Deployment Group – Define how the deployment behaves. In the deployment group, you set the previously-defined Application, Deployment Configuration, on which EC2 instances to run the deployment and any AutoScaling configuration.
  • Deployment Configuration – There are three default configurations provided by CodeDeploy: CodeDeployDefault.OneAtATimeCodeDeployDefault.AllAtOnce and CodeDeployDefault.HalfAtATime. You can describe custom configurations as well.
  • Lifecycle Events – Each deployment triggers certain events that run in a pre-defined sequence. The actual code that is run is defined in the appspec.yml using hooks section of the file. Here are the events that get triggered as part of a typical CodeDeploy deployment.
    • ApplicationStop
    • DownloadBundle
    • BeforeInstall
    • Install
    • AfterInstall
    • ApplicationStart
    • ValidateService

In order for CodeDeploy to work, you need to install the CodeDeploy agent on each EC2 instance on which you’re running CodeDeploy.  CodeDeploy communicates with the agents via HTTPS over port 443. The agent contains code that has CodeDeploy domain-specific knowledge and uses the defined configuration to run through its lifecycle events. 

ManagementTools_CloudFormation  Executing the CloudFormation Template

Below, you see an example of running the template that I’ve defined. You’ll need to define your own stack name and EC2KeyPairName. From your AWS CLI, run a command to launch the CloudFormation stack. You can also launch the same using the CloudFormation console.

aws cloudformation create-stack --stack-name MyStackName --template-url https://s3.amazonaws.com/stelligent-training-public/public/codedeploy/codedeploy-master.json
--region us-east-1 --disable-rollback --capabilities="CAPABILITY_IAM"
--parameters ParameterKey=S3Bucket,ParameterValue=aws-codedeploy-us-east-1
ParameterKey=S3Key,ParameterValue=public/Sample_Linux_App.zip
ParameterKey=EC2TagValue,ParameterValue=CodeDeployEC2Tag
ParameterKey=EC2KeyPairName,ParameterValue=MyEC2KeyPairName

It’ll take about 10-15 minutes to launch the stacks that launch the EC2 instance, install the CodeDeploy agent, configure and run a deployment of the application. You can visually verify the application works by going to the CodeDeploy console (shown in Figure 1), select an application, then a deployment and click on the link under the Instance ID column. From the EC2 console, find the Public IP and prepend http:// to it from your web browser. codedeploy-deployment

Figure 1: AWS CodeDeploy deployment status

Architecture

There are two CloudFormation templates to define the EC2 instances, S3 Distribution Location, IAM and CodeDeploy resources along with its overall orchestration via nested stacks. They are:

  • codedeploy-master.json – The master template orchestrates the execution of the other CloudFormation templates using nested CloudFormation stacks and the DependsOn attribute. It uses the Outputs from one stack as input parameters to the next calling template. Using the DependsOn attribute ensures that the resources have been provisioned prior to calling the next template.
  • codedeploy-deployment.json – Automates the provisioning of AWS CodeDeploy resources including the CodeDeploy Application and Deployment. It points to an S3 bucket and key where the sample application zip file is stored.

The resulting infrastructure is illustrated in Figure 2. codedeploy-arch

Figure 2: Infrastructure Architecture Diagram

Implementation

There are several parts to automating the process of downloading the sample application, provisioning EC2 instances with the CodeDeploy agent, provisioning CodeDeploy to create an application and to run a deployment for that application using CodeDeploy. The first example snippet below can be found in the codedeploy-master.json CloudFormation template. This launches a new stack from the AWS CloudFormation template that provisions EC2 instances and installs the CodeDeploy agent.

    "CodeDeployEC2InstancesStack":{
      "Type":"AWS::CloudFormation::Stack",
      "Properties":{
        "TemplateURL":"http://s3.amazonaws.com/aws-codedeploy-us-east-1/templates/latest/CodeDeploy_SampleCF_Template.json",
        "TimeoutInMinutes":"60",
        "Parameters":{
          "TagValue":{
            "Ref":"EC2TagValue"
          },
          "KeyPairName":{
            "Ref":"EC2KeyPairName"
          }
        }
      }
    },

The next snippet from the same codedeploy-master.json template uses the EC2 tag value that was set in the first stack as a way for this template to determine on which EC2 instances it will run the CodeDeploy deployment. It also uses the DependsOn attribute to ensure that the stack has been created before attempting to launch this stack – since it requires resources from the first stack.

    "CodeDeploySimpleStack":{
      "Type":"AWS::CloudFormation::Stack",
      "DependsOn":"CodeDeployEC2InstancesStack",
      "Properties":{
        "TemplateURL":"https://s3.amazonaws.com/stelligent-training-public/public/codedeploy/codedeploy-deployment.json",
        "TimeoutInMinutes":"60",
        "Parameters":{
          "TagValue":{
            "Ref":"EC2TagValue"
          },
          "RoleArn":{
            "Fn::GetAtt":[
              "CodeDeployEC2InstancesStack",
              "Outputs.CodeDeployTrustRoleARN"
            ]
          },
          "Bucket":{
            "Ref":"S3Bucket"
          },
          "Key":{
            "Ref":"S3Key"
          }
        }
      }
    }

In the codedeploy-deployment.json template snippet below, I’m defining the CodeDeploy application. This generates a unique identifier that’s used in the next resource definition in the template.

    "MyApplication":{
      "Type":"AWS::CodeDeploy::Application"
    },

In the snippet below (also from codedeploy-deployment.json), I’m defining how my deployment will behave using the CodeDeploy Deployment Group. Once again, I use the DependsOn attribute to ensure that the CodeDeploy application exists prior to provisioning the deployment group since it needs to already exist. Then, I find the application bundle in S3 using the S3Location property. From the command-line execution snippet described earlier, you’ll see that I’m passing aws-codedeploy-us-east-1 as the S3 bucket parameter and samples/latest/SampleApp_Linux.zip as the S3 Key. This resolves to https://s3.amazonaws.com/aws-codedeploy-us-east-1/samples/latest/SampleApp_Linux.zip.

    "MyDeploymentGroup":{
      "Type":"AWS::CodeDeploy::DeploymentGroup",
      "DependsOn":"MyApplication",
      "Properties":{
        "ApplicationName":{
          "Ref":"MyApplication"
        },
        "Deployment":{
          "Description":"First time",
          "IgnoreApplicationStopFailures":"true",
          "Revision":{
            "RevisionType":"S3",
            "S3Location":{
              "Bucket":{
                "Ref":"Bucket"
              },
              "BundleType":"Zip",
              "Key":{
                "Ref":"Key"
              }
            }
          }
        },
        "Ec2TagFilters":[
          {
            "Key":{
              "Ref":"TagKey"
            },
            "Value":{
              "Ref":"TagValue"
            },
            "Type":"KEY_AND_VALUE"
          }
        ],
        "ServiceRoleArn":{
          "Ref":"RoleArn"
        }
      }

Troubleshooting

As you’re experimenting with CodeDeploy and automating the provisioning of CodeDeploy in CloudFormation, you’ll likely experience a few problems along the way. There were a couple of useful suggestions provided to me by AWS Support.

      • Get the logs – SSH into the EC2 instances on which the CodeDeploy agent was installed and view the contents of the /opt/codedeploy-agent/deployment-root and /var/log/ directories. You might choose to zip up the directories and download them for easy viewing as demonstrated below.

zip -r varlog.zip /var/log

zip -r deployment-root.zip /opt/codedeploy-agent/deployment-root

      • View in CloudWatch – I haven’t tried this yet, but there’s an interesting article on the AWS DevOps blog about viewing the CodeDeploy logs in CloudWatch: View AWS CodeDeploy logs in Amazon CloudWatch console
      • Turn off Auto Rollback in CloudFormation – When you’re using the AWS CloudFormation Console and you accept all the default options, it will auto rollback all the stacks. This makes it more difficult to troubleshoot when something does go wrong. Instead, you might choose to call from the command line as shown in the example below. You can still turn off auto rollback using the console, but it’s easier to forget.
aws cloudformation create-stack --stack-name MyStackName --template-body file://mystack.json --region us-east-1 --disable-rollback

Feature Backlog

There’s some additional functionality that I plan to implement in the coming weeks as described below.

      • Use Elastic Load Balancing and AutoScaling so that it’s a more highly available solution with a single endpoint
      • Create a deployment pipeline using AWS CodePipeline and automate the provisioning of the pipeline in AWS CloudFormation
      • Implement CodeDeploy in CloudFormation as part of the Dromedary Demo pipeline.
      • Include re-deployment to the same EC2 instances when an application change occurs

Useful Resources

 

Provisioning AWS CodePipeline with CloudFormation

Since our entire focus at Stelligent is to help our customers apply Continuous Delivery in Amazon Web Services (AWS), we were really excited when we learned that AWS CloudFormation added support for AWS CodePipeline. I spent some time upgrading our Dromedary demo scripts to incorporate this new functionality. This article describes the process we applied in making the necessary changes along with some bonus functionality we were able to implement a more fully automated solution – thanks to Corey at AWS Support.

When the AWS CodePipeline team released its Continuous Delivery service in July 2015, they provided a custom JSON-based DSL for it which we used to automate the provisioning of CodePipeline itself but we didn’t get it into the 100% automated state for which we’d been hoping. It was close, but were “chomping at the bit” for the time when AWS would begin providing CloudFormation support. Thankfully, now’s the time.

A couple of Stelligent’s engineers wrote the first version of the demo that we used at the “Infrastructure as Code” breakout session as part of AWS re:Invent 2015 so they’d already been through some of the things I learned while adding and updating our scripts.

Below, you’ll find the steps for getting this demo running on your own AWS account. All of the code is freely available and open source.

Running from CloudFormation

Click on the Launch Stack button below.


Enter the required parameters. In particular: Stack nameDDBTableNameGitHubToken, KeyName and ProdHostedZone. See the figure below and complete the rest of the steps to launch the stack. It’ll take approximately 20 minutes to launch the stack and then launch a CodePipeline pipeline instance that launches an application stack. You can see the README for other ways to run this template from CloudFormation.

dromedary_master

Once all the required AWS resources are bootstrapped, it automatically launches a pipeline instance in CodePipeline as shown below.

codepipeline_cfn

Running from the Command Line

You’ll need to run the command below from a computer on which the AWS CLI has been installed.

Launching CloudFormation stack from command line

aws cloudformation create-stack 
--stack-name DromedaryStack  
--template-body https://raw.githubusercontent.com/stelligent/dromedary/master/pipeline/cfn/dromedary-master.json 
--region us-east-1 
--disable-rollback --capabilities="CAPABILITY_IAM" 
--parameters ParameterKey=KeyName,ParameterValue=YOURKEYPAIR 
    ParameterKey=Branch,ParameterValue=master 
    ParameterKey=BaseTemplateURL,ParameterValue=https://s3.amazonaws.com/stelligent-training-public/master/ 
    ParameterKey=GitHubUser,ParameterValue=YOURGITHUBUSER 
    ParameterKey=GitHubToken,ParameterValue=YOURGITHUBTOKEN 
    ParameterKey=DDBTableName,ParameterValue=YOURUNIQUEDDBTABLENAME 
    ParameterKey=ProdHostedZone,ParameterValue=.YOURHOSTEDZONE

For more information, you can see the README at https://github.com/stelligent/dromedary.

The steps I went through to create this CloudFormation template were fairly straightforward. First, I started with a CloudFormation template that I had implemented for another effort and removed most everything except for the core structure including the AWSTemplateFormatVersion, an empty Parameters block and an empty Resources block. The core Resources Type for CodePipeline is – as you might’ve guessed – AWS::CodePipeline::Pipeline. After this, I got the name of an existing pipeline that we’d created using the AWS CodePipeline JSON DSL from our AWS CodePipeline and ran this command:

aws codepipeline get-pipeline --name YOUR_PIPELINE_NAME > pipeline.json

This provided a JSON structure with all the stages and actions already defined in code so I was able to copy and paste within my AWS::CodePipeline::Pipeline Resources construct in the CloudFormation template. Since the CodePipeline DSL produces a slightly different case than CloudFormation, I needed to update the letter case for several properties to conform to the CloudFormation standard.

Then, I added several parameters to the template including GitHubToken and GitHubUser. Finally, I ran through several testing scenarios. Be sure to run through all the steps in README as well since it’s important that you enable security for Jenkins.

Summary

Essentially, until AWS implements a service in CloudFormation, we don’t really consider it a service we can use in the manner we like so we’re really happy that CodePipeline can now be provisioned in CloudFormation. Moreover, when implementing the CodePipeline provisioning in CloudFormation, we configured it so that all of CodePipeline including the Github configuration is automated so there are no manual steps anymore (except for enabling Jenkins Global Security). If you have any questions or comments, you can contact us at info@stelligent.com or consult the contact information at https://github.com/stelligent/dromedary.

Stelligent is an expert in deploying continuous delivery pipelines in AWS. If you are looking for help moving your applications into CodePipeline, or another type of continuous delivery pipeline, we can definitely help you with that. Reach out to us here.

Useful Resources

Stelligent AWS Continuous Delivery Demo Screencast

See the YouTube screencast and transcript below of a Continuous Deployment pipeline demonstration for a Node.js application using AWS services such as EC2, DynamoDB, Route 53, ENI and VPC and tools such as AWS CodePipeline, Jenkins, Chef and AWS CloudFormation. Open source code is available at https://github.com/stelligent/dromedary

Transcript
In this screencast, you’ll see a live demonstration of a system that uses Continuous Deployment of features based on a recent code commit to Github.

You can access this live demo right now by going to demo.stelligent.com. All of the code is available in open source form by going to https://github.com/stelligent/dromedary.

This is a simple Node.js application in which you can click on any of the colors to “vote” for that color and see the results of your votes and others in real time. While it’s a simple application, it uses many of the types of services and tools that you might define in your enterprise systems such as EC2, Route 53, DynamoDB, VPC, Elastic IP and ENI, and it’s built, deployed, tested and released using CloudFormation, Chef, CodePipeline and Jenkins – among other tools.

So, I want you to imagine there’s several engineers on a team that have a dashboard like this on a large monitor showing AWS CodePipeline. CodePipeline is a service released by AWS in July 2015. With CodePipeline, you can model your Continuous Delivery and Continuous Deployment workflows – from the point at which someone commits new code to a version-control repository until it gets released to production.

You can see it shows there’s a failure associated with the infrastructure test. So, let’s take a look at the failure in Jenkins.

Many of you may already be familiar with Jenkins as it’s a Continuous Integration server. In the context of this demo, we’re using CodePipeline to orchestrate our Continuous Deployment workflow and Jenkins to perform the execution of all the code that creates the software system.

Now, the reason for this failure is because we’ve written a test to prevent just anyone from SSH’ing into the EC2 instance from a random host. So, I’m going to put my infrastructure developer hat on and look at the test that failed.

This is an RSpec test to test whether port 22 is accessible to any host. This test gets run every time someone commits any code to the version-control repository.

Based on this test failure, I’m going to look at the CloudFormation template that provisions the application infrastructure. The app_instance.json CloudFormation template defines the parameters, conditions, mappings and resources for the application infrastructure. With CloudFormation there are over 100 built-in AWS resources we define in code. Here, I’m looking at the resource in this template that defines the security group for the EC2 instance that’s hosting the application.

I’m going to update the CIDR block to a specific host using the /32 notation so that only I can access the instance that hosts the application.

Normally, I might wait for the changes made by the other infrastructure developer to successfully pass through the pipeline, but in this case, I’m just going to make my application code changes as well.

So, putting my application developer hat on, I’m going to make an application code change by adding a new color to the pie chart that gets displayed in our application. So, I’ll add the color orange to this pie chart. I’m also going to update my unit and functional tests so that they pass.

Now, you might’ve noticed something in the application. I’m not using SSL. So you go to http, instead of https. That’s insecure. So, I’m going to make some code changes to enable SSL in my application.

Ok. So, I’ve committed my changes to Git and it’s going to run through all the stages and actions in CodePipeline.

…and commit it to Git

So, CodePipeline is polling Github looking for any changes. Since I just committed some new changes, CodePipeline will discover those changes and begin the execution of the defined jobs in Jenkins.

Now, you’ll notice that CodePipeline picked up these changes – the security/infrastructure, SSL, changes along with the application code changes will be built, tested, deployed as part of this deployment pipeline.

You’ll see that there are a number of different stages here, each consisting of different actions. A stage is simply a logical grouping of actions, and is largely dictated by your application or service. The actions themselves call out to other services. There are four types of built-in actions within CodePipeline: source, build, test and deploy. You can also define custom actions in CodePipeline

Ok, now CodePipeline has gone through all of its stages successfully.

You can see that I added the new color, orange, and all my unit and infrastructure tests passed.

It spun up a new infrastructure and used the Blue/Green Deployment pattern to switch to the new environment using Route 53. With a blue-green deployment, we’ve got a production environment (we’ll call this “blue”) and a pre-production environment that looks exactly like production (and we’ll call this “green”). We commit the changes to Github and CodePipeline orchestrates these changes by spinning up the green environment and then uses Route 53 to move all traffic to green. Or, another way of putting this is that you’re switching between production and pre-production environments. Anyway, with this approach you can continue serving users without them experiencing any downtime. You can also potentially rollback to that blue environment can become production again if anything goes wrong with the deployment.

So, let’s summarize what happened…I made application, infrastructure and security changes as code and committed them to Git. It automatically went through a deployment pipeline using CodePipeline and Jenkins – which was also defined in code – It built, ran unit and functional tests, and stored the distro, launched an environment from code and deployed the application – using CloudFormation and Chef. As part of this pipeline, it also ran infrastructure tests as code and deployed to production without anyone lifting another finger. This way you get feedback as soon as an error occurs and only release to production when it passes all these checks. You can do the same thing in your enterprises. From commit to production in minutes or hours instead of days or weeks. Just think about what’s possible when you’re releases become a non-event like this!

AWS CodePipeline released, and there was much rejoicing

AWS can be a big tease. Last November at re:Invent, they announced AWS CodePipeline, an orchestration engine for builds. As they described it, I got more and more excited, up until the point they said it wouldn’t be released in mid-2015. Well, today might as well be Christmas, because CodePipeline has just been released and I am already figuring out how I can use it all over the place.

aws_code_pipeline

AWS CodePipeline is part of the Amazon Web Services suite of DevOps tools, and provides orchestration of continuous delivery pipelines in the cloud. It fills the void between where your traditional continuous integration solution ends and your deployments begin (which if you’ve caught me at a conference or meetup in the past few years, you’ve heard me complaining about). It handles orchestrating every step of your build, test, and deploy process, from source code to server deployments.

CodePipeline uses four core building blocks, called providers, to put together your pipeline. The first is your Source Provider, which allows you to define the location of the source code for your application. At this time, CodePipeline supports two source providers: artifacts zipped up and uploaded to an S3 bucket, and repositories hosted on GitHub.

Another type of provider is the Deployment Provider. This provider will deploy your application to your servers. CodePipeline supports two AWS deployment providers: AWS CodeDeploy and AWS Elastic Beanstalk. Either will need to be configured independently, and then hooked in as a CodePipeline stage.

The final types of provider are the most interesting: the Build and Test Providers. With these providers you can execute the logic for building and testing your application. As of right now, CodePipeline supports these providers in a couple different ways. For build providers, there are Jenkins and CloudBees Plugins. Once installed on your Jenkins server, you can create jobs that will poll CodePipeline and execute actions that are part of the pipeline.

If you don’t currently have a Jenkins server, and don’t particularly feel like setting one up, CodePipeline also supports custom actions so you can plug into whatever service you are working with.

For testing, you can still delegate to Jenkins or a customer provider, but CodePipeline comes with built in support for the following test frameworks: Apica and Blazemeter for load testing, Ghost Inspector for UI testing, and Runscope for API monitoring.

To get CodePipeline configured and running, you’ll need to set up a few things. First, you’ll need to install the aforementioned CodePipeline Jenkins Plugin on your Jenkins server if you’re going to be using that. This can be done from your Jenkins Manage Plugins screen, or you can upload the plugin manually following these steps.

You’ll also need an AWS IAM Role configured for CodePipeline to execute AWS actions on your behalf (mostly messing around with S3 buckets). An example of the role you’ll need to available here. When you create your pipeline, you’ll need to reference that role in its configuration.

In addition to those, if you want CodePipeline to orchestrate deployments for you, you’ll need to configure your application to be deployed via CodeDeploy or Elastic Beanstalk. Which one you pick decides on the complexity of your app: simpler applications will do very will with Elastic Beanstalk, but for applications that require a bit of configuration to get going will be better served by using CodeDeploy.

With all those parts in place, setting up your first CodePipeline is as simple as using the wizard to glue all those parts together. Alternatively, CodePipeline is supported by the AWS CLI and the AWS API, and the SDKs have already started rolling out support for it.  Now all I have left to do is sit patiently until it’s supported by CloudFormation.:-)

Stelligent is an expert in deploying continuous delivery pipelines in AWS. If you are looking for help moving your application into CodePipeline, or another type of continuous delivery pipeline, we can definitely help you with that. Reach out to us here.