Skip to content

Instantly share code, notes, and snippets.

@jed

jed/deploy.sh

Last active Mar 30, 2021
Embed
What would you like to do?
Using AWS CloudFormation to deploy an edge lambda
#!/bin/sh
aws cloudformation deploy \
--template-file stack.yaml \
--stack-name edge-lambda-test \
--capabilities CAPABILITY_IAM \
--parameter-overrides Nonce=$RANDOM
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
Nonce:
Type: String
Outputs:
Host:
Value: !GetAtt Distribution.DomainName
Resources:
Bucket:
Type: AWS::S3::Bucket
Distribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
Origins:
- Id: !Ref Bucket
DomainName: !GetAtt Bucket.DomainName
S3OriginConfig: {}
DefaultCacheBehavior:
TargetOriginId: !Ref Bucket
ForwardedValues:
QueryString: true
ViewerProtocolPolicy: redirect-to-https
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !GetAtt IndexLambdaVersion.FunctionArn
IndexLambda:
Type: AWS::Lambda::Function
Properties:
Role: !GetAtt IndexLambdaRole.Arn
Runtime: nodejs6.10
Handler: index.handler
Code:
ZipFile: |
exports.handler = (event, ctx, cb) => {
const status = '200'
const headers = {
'content-type': [{
key: 'Content-Type',
value: 'application/json'
}]
}
const body = JSON.stringify(event, null, 2)
const response = {status, headers, body}
cb(null, response)
}
IndexLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
- edgelambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
IndexLambdaVersion:
Type: Custom::LatestLambdaVersion
Properties:
ServiceToken: !GetAtt PublishLambdaVersion.Arn
FunctionName: !Ref IndexLambda
Nonce: !Ref Nonce
# Custom resource for getting latest version of a lambda,
# as required by CloudFront.
PublishLambdaVersion:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Runtime: nodejs6.10
Role: !GetAtt PublishLambdaVersionRole.Arn
Code:
ZipFile: |
const {Lambda} = require('aws-sdk')
const {send, SUCCESS, FAILED} = require('cfn-response')
const lambda = new Lambda()
exports.handler = (event, context) => {
const {RequestType, ResourceProperties: {FunctionName}} = event
if (RequestType == 'Delete') return send(event, context, SUCCESS)
lambda.publishVersion({FunctionName}, (err, {FunctionArn}) => {
err
? send(event, context, FAILED, err)
: send(event, context, SUCCESS, {FunctionArn})
})
}
PublishLambdaVersionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: PublishVersion
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: lambda:PublishVersion
Resource: '*'
@jamesmoriarty

This comment has been minimized.

Copy link

@jamesmoriarty jamesmoriarty commented Jul 22, 2019

Thanks. Very concise.

@dcbauer

This comment has been minimized.

Copy link

@dcbauer dcbauer commented Oct 9, 2019

Thanks. Got me up and running quick.

@mrowles

This comment has been minimized.

Copy link

@mrowles mrowles commented Jan 5, 2020

Thanks for this - I assume this only works in 'us-east-1'?

@MatteoGioioso

This comment has been minimized.

Copy link

@MatteoGioioso MatteoGioioso commented Jan 7, 2020

How long does the deployment takes every time you change the lambda code? As my understanding it will take approximately 20-30 minutes to replicate the function every code change.

@tjovicic

This comment has been minimized.

Copy link

@tjovicic tjovicic commented Feb 5, 2020

Needs to upgrade node version to 10.x and the region must be us-east-1

@DheerajArora5

This comment has been minimized.

Copy link

@DheerajArora5 DheerajArora5 commented Apr 27, 2020

Thanks jed , this post has been alot helpful !!

@snowake4me

This comment has been minimized.

Copy link

@snowake4me snowake4me commented Oct 14, 2020

Thanks much for this gist jed, and the highly-functional 'custom resource' - very helpful for my purposes, and a place I've found AWS documentation and support lacking. I deployed with an updated Runtime: of nodejs12.x with success.

One question - about the use of the nonce. Is this to provide idempotence, allowing multiple instances in a given account, or some other purpose I'm just not grokking? Thanks again!!

@jed

This comment has been minimized.

Copy link
Owner Author

@jed jed commented Oct 14, 2020

Thanks much for this gist jed, and the highly-functional 'custom resource' - very helpful for my purposes, and a place I've found AWS documentation and support lacking. I deployed with an updated Runtime: of nodejs12.x with success.

One question - about the use of the nonce. Is this to provide idempotence, allowing multiple instances in a given account, or some other purpose I'm just not grokking? Thanks again!!

i haven't used this in a while, but IIRC the nonce is to force a redeploy of the template, otherwise CloudFormation would just tell you that the template hasn't changed.

@nom3ad

This comment has been minimized.

Copy link

@nom3ad nom3ad commented Oct 28, 2020

I was reading through AWS docs.
Seems now we get Lambda Versioning out of the box.
May be this could be modified as follows without using the Cloudformation custom resource and an additional IAM Role.

VersionedIndexLambda:
    Type: 'AWS::Lambda::Version'
    Properties:
        FunctionName: !Ref IndexLambda

Distribution:
    Type: AWS::CloudFront::Distribution
      ......
        ......
          LambdaFunctionAssociations:
             - EventType: viewer-request
               LambdaFunctionARN: !Ref VersionedIndexLambda
 

Anybody tried this way?

@LpmRaven

This comment has been minimized.

Copy link

@LpmRaven LpmRaven commented Nov 17, 2020

I was reading through AWS docs.
Seems now we get Lambda Versioning out of the box.
May be this could be modified as follows without using the Cloudformation custom resource and an additional IAM Role.

VersionedIndexLambda:
    Type: 'AWS::Lambda::Version'
    Properties:
        FunctionName: !Ref IndexLambda

Distribution:
    Type: AWS::CloudFront::Distribution
      ......
        ......
          LambdaFunctionAssociations:
             - EventType: viewer-request
               LambdaFunctionARN: !Ref VersionedIndexLambda
 

Anybody tried this way?

@nom3ad I have tried this, I have found that the lambda version does not update when you redeploy your code. Jed has gifted you the solution above.

@haydenk

This comment has been minimized.

Copy link

@haydenk haydenk commented Nov 22, 2020

@LpmRaven Because AWS::Lambda::Version resources can only be created or delete not updated.

So, what you would have to do is create a second version to replace the current version.

First Deploy

VersionedIndexLambdaA:
    Type: 'AWS::Lambda::Version'
    Properties:
        FunctionName: !Ref IndexLambda

Distribution:
    Type: AWS::CloudFront::Distribution
      ......
        ......
          LambdaFunctionAssociations:
             - EventType: viewer-request
               LambdaFunctionARN: !Ref VersionedIndexLambdaA

Second Deploy - Will create a second lambda version now and deploy it to the distribution

VersionedIndexLambdaA:
    Type: 'AWS::Lambda::Version'
    Properties:
        FunctionName: !Ref IndexLambda

VersionedIndexLambdaB:
    Type: 'AWS::Lambda::Version'
    Properties:
        FunctionName: !Ref IndexLambda

Distribution:
    Type: AWS::CloudFront::Distribution
      ......
        ......
          LambdaFunctionAssociations:
             - EventType: viewer-request
               LambdaFunctionARN: !Ref VersionedIndexLambdaB

It has some oddities to it but it could be better than creating a whole other lambda just to deploy another lambda.

@LpmRaven

This comment has been minimized.

Copy link

@LpmRaven LpmRaven commented Nov 24, 2020

@LpmRaven Because AWS::Lambda::Version resources can only be created or delete not updated.

So, what you would have to do is create a second version to replace the current version.

First Deploy

VersionedIndexLambdaA:
    Type: 'AWS::Lambda::Version'
    Properties:
        FunctionName: !Ref IndexLambda

Distribution:
    Type: AWS::CloudFront::Distribution
      ......
        ......
          LambdaFunctionAssociations:
             - EventType: viewer-request
               LambdaFunctionARN: !Ref VersionedIndexLambdaA

Second Deploy - Will create a second lambda version now and deploy it to the distribution

VersionedIndexLambdaA:
    Type: 'AWS::Lambda::Version'
    Properties:
        FunctionName: !Ref IndexLambda

VersionedIndexLambdaB:
    Type: 'AWS::Lambda::Version'
    Properties:
        FunctionName: !Ref IndexLambda

Distribution:
    Type: AWS::CloudFront::Distribution
      ......
        ......
          LambdaFunctionAssociations:
             - EventType: viewer-request
               LambdaFunctionARN: !Ref VersionedIndexLambdaB

It has some oddities to it but it could be better than creating a whole other lambda just to deploy another lambda.

Yes @haydenk, but unfortunately that is not at all practical for continuous deployment. Its much easier to have a lambda just publish a new version every time you make code changes.

@LpmRaven

This comment has been minimized.

Copy link

@LpmRaven LpmRaven commented Nov 25, 2020

So I have taken some of the code here and turned it into a CI/CD pipeline that will update to a new lambda@edge version every time code is released. https://github.com/LpmRaven/lambda-edge-language-region-redirect/tree/master/cloudformation

Hopefully, it will help someone as I had to generate a random nonce in 'buildspec' to force update the version number.

@haydenk

This comment has been minimized.

Copy link

@haydenk haydenk commented Nov 28, 2020

@LpmRaven I agree it's awkward but I wouldn't say it's not practical. The current solution is relying on an outside resource to finish the deployment process after CloudFormation update has been completed. Keeping it in CloudFormation allows you easily to revert to VersionedIndexLambdaA if for some reason VersionedIndexLambdaB has some unintended side effects, for some reason, with another deployment.

Just to re-emphasis, I do admit it is a very awkward solution. Awkward enough to say either solution would be fine, it's just a matter of what you're willing to deal with long term.

@LpmRaven

This comment has been minimized.

Copy link

@LpmRaven LpmRaven commented Nov 28, 2020

@haydenk Take a look at the link I provided in the previous reply. You can revert to a previous lambda version by hardcoding the arn attached to the CloudFront distribution ...and you don't need to change cloudformation every time you update your lambda@edge.

@VladimirGrenaderov

This comment has been minimized.

Copy link

@VladimirGrenaderov VladimirGrenaderov commented Nov 29, 2020

Lambda update problem already solved in AWS SAM - see https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-generated-resources-function.html#sam-specification-generated-resources-function-autopublishalias.

Just add AutoPublishAlias to AWS::Serverless::Function and SAM will create hash-based versions automatically than update CloudFront bindings.

Just rebuild lambda and run sam deploy:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment