Skip to content

Instantly share code, notes, and snippets.

@alexcasalboni
Last active March 22, 2023 05:27
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save alexcasalboni/242071568a6adde805027345cfbb6d99 to your computer and use it in GitHub Desktop.
Save alexcasalboni/242071568a6adde805027345cfbb6d99 to your computer and use it in GitHub Desktop.
AWS SAM Demo - Automatic Rollback for AWS Lambda with AWS CodeDeploy

How to Build and Deploy Serverless Apps [AWS Summit]

This demo was presented at the AWS Summit @ Cape Town on Jul 12th.

You can find the slides here.

What's included in this Gist?

  • index.js: The node.js code used for AWS Lambda
  • sam_template.yaml: The AWS SAM template in YAML format (i.e. CloudFormation)
  • scripts: Four scripts used to showcase the live deployment rollback.
    • script_deploy.sh: It packages and deploys the AWS SAM template, then it shows the ENDPOINT Output
    • script_poll.sh: It polls a given URL forever and stores responses and timings in a txt file
    • script_tail.sh: It tails the txt file that contains responses and timings to show live responses
    • script_stats.sh: It reads the txt file and counts OK and KO responses to show some statistics

Feel free to reach out to me on Twitter if you need help or if you just want to say hi :)

'use strict';
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();
const tableName = process.env.TABLE_NAME;
const createResponse = (statusCode, msg) => {
return {
statusCode: statusCode,
body: JSON.stringify({message: msg})
}
};
const dbGet = (params) => { return dynamo.get(params).promise() };
exports.test = (event, context, callback) => {
// throw new Error("KO");
let params = {
TableName: tableName,
Key: {
id: "test"
}
};
dbGet(params).then( (data) => {
if (!data.Item) {
return callback(null, createResponse(404, "ITEM NOT FOUND"));
}
callback(null, createResponse(200, data.Item.message));
}).catch( (err) => {
console.log(`GET ITEM FAILED WITH ERROR: ${err}`);
callback(null, createResponse(500, err));
});
};
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: Safe deployments Demo
Resources:
Table:
Type: AWS::Serverless::SimpleTable
TestFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.test
Runtime: nodejs6.10
CodeUri: ./
Policies: AmazonDynamoDBReadOnlyAccess
Environment:
Variables:
TABLE_NAME: !Ref Table
Events:
GetResource:
Type: Api
Properties:
Path: /test
Method: get
AutoPublishAlias: live
DeploymentPreference:
Type: Linear10PercentEvery1Minute
Alarms:
- !Ref AliasErrorMetricGreaterThanZeroAlarm
- !Ref LatestVersionErrorMetricGreaterThanZeroAlarm
AliasErrorMetricGreaterThanZeroAlarm:
Type: AWS::CloudWatch::Alarm
DependsOn: TestFunction
Properties:
AlarmName: !Sub alarm-sam-demo-alias-${TestFunction}
AlarmDescription: "deployment alarm to check for errors in the function"
ComparisonOperator: GreaterThanOrEqualToThreshold
Dimensions:
- Name: FunctionName
Value: !Ref TestFunction
EvaluationPeriods: 1
MetricName: Errors
Namespace: AWS/Lambda
Period: 60
Statistic: Sum
Threshold: 1
LatestVersionErrorMetricGreaterThanZeroAlarm:
Type: AWS::CloudWatch::Alarm
DependsOn: TestFunction
Properties:
AlarmName: !Sub alarm-sam-demo-latestversion-${TestFunction}
AlarmDescription: "alarm to check for errors in the function"
ComparisonOperator: GreaterThanOrEqualToThreshold
TreatMissingData: missing
Dimensions:
- Name: FunctionName
Value: !Ref TestFunction
- Name: Resource
Value: !Join [":", [!Ref TestFunction, !Select ["7", !Split [":", !Ref TestFunction.Version]]]]
EvaluationPeriods: 1
MetricName: Errors
Namespace: AWS/Lambda
Period: 60
Statistic: Sum
Threshold: 1
Outputs:
ENDPOINT:
Description: "API endpoint URL for Prod environment"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/test"
#!/bin/bash
# variables
TEMPLATE_IN=sam_template.yaml
TEMPLATE_OUT=template-packaged.yaml
S3_BUCKET=serverless-sam-demos
STACK_NAME=ServerlessSAMDemo
REGION=eu-west-1
# package (upload artifact to S3)
echo "SAM is now packaging..."
sam package --template-file $TEMPLATE_IN --s3-bucket $S3_BUCKET --output-template-file $TEMPLATE_OUT --region $REGION
# deploy (CloudFormation changesets)
echo "SAM is now deploying..."
sam deploy --template-file $TEMPLATE_OUT --stack-name $STACK_NAME --capabilities CAPABILITY_IAM --region $REGION
# fetch and show CloudFormation output
ENDPOINT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME --region $REGION --query 'Stacks[0].Outputs[?OutputKey==`ENDPOINT`].OutputValue' --output text)
echo "ENDPOINT: $ENDPOINT"
#!/bin/bash
# loop forever
for ((;;))
do
# print a same-line dot
echo -e ".\c"
# fetch the given URL and append the response to a txt file
curl $1 -w " (%{time_total}s) \n" >> data.txt 2>/dev/null
done
#!/bin/bash
# count lines
KO=$(grep "Internal server error" data.txt | wc -l)
OK=$(grep "OK" data.txt | wc -l)
# compute percntage
PERCKO=$(echo "$KO * 100 / ($KO + $OK)" | bc)
PERCOK=$(echo "$OK * 100 / ($KO + $OK)" | bc)
# show stats
echo "OK requests: $OK ($PERCOK%)"
echo "KO requests: $KO ($PERCKO%)"
#!/bin/bash
# empty file
echo "" > data.txt
# tail and wait for new daata
tail -F data.txt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment