Skip to content

Instantly share code, notes, and snippets.

@jed
Last active April 24, 2024 15:45
Show Gist options
  • Save jed/56b1f58297d374572bc51c59394c7e7f to your computer and use it in GitHub Desktop.
Save jed/56b1f58297d374572bc51c59394c7e7f to your computer and use it in GitHub Desktop.
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: '*'
@DheerajArora5
Copy link

Thanks jed , this post has been alot helpful !!

@snowake4me
Copy link

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
Copy link
Author

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
Copy link

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
Copy link

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
Copy link

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
Copy link

@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
Copy link

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
Copy link

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
Copy link

@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.

@koshic
Copy link

koshic 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

@guerrerocarlos
Copy link

guerrerocarlos commented Jun 3, 2021

Has @haydenk points out, just adding a new AWS::Lambda::Version works, and it's actually practical because you can remove the previous AWS::Lambda::Version in the same update, making it compatible with continuous deployment. Also the AutoPublishAlias is only available for SAM not CloudFormation.

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