AWS Serverless: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
 
Line 238: Line 238:
service:  myservice                        # name-of-service
service:  myservice                        # name-of-service


'''custom''':                                                                      # These are custom variables which are used throughout the template
custom:                                                                      # These are custom variables which are used throughout the template
   stage: ${opt:stage, self:provider.stage}                                  # Stage
   stage: ${opt:stage, self:provider.stage}                                  # Stage
   serverless-offline:                                                        # Configuration for running locally
   serverless-offline:                                                        # Configuration for running locally
Line 260: Line 260:
     dev: "api://XXX"                                                        # The value is defined in AWS and must match the Azure Registration's Application ID URI
     dev: "api://XXX"                                                        # The value is defined in AWS and must match the Azure Registration's Application ID URI
     prod: "api://XXX"
     prod: "api://XXX"


plugins:                # Plugins to run in Serverless Framework
plugins:                # Plugins to run in Serverless Framework
   - serverless-bundle
   - serverless-bundle
   - serverless-offline
   - serverless-offline


provider:
provider:
   name: aws                                                // Provider
   name: aws                                                # Provider
   stage: dev                                                // Default stage
   stage: dev                                                # Default stage
   profile: ${self:custom.aws-profile.${self:custom.stage}}  // Profile
   profile: ${self:custom.aws-profile.${self:custom.stage}}  # Profile
   runtime: nodejs12.x                                      // Hashing function to use
   runtime: nodejs12.x                                      # Hashing function to use
   lambdaHashingVersion: 20201221                            // Not required anymore
   lambdaHashingVersion: 20201221                            # Not required anymore
   region: ap-southeast-2                                    // Region to deploy to
   region: ap-southeast-2                                    # Region to deploy to
   tracing:
   tracing:
     apiGateway: true                                        // Debug option for API   
     apiGateway: true                                        # Debug option for API   
     lambda: true                                            // Debug option for Lambda function
     lambda: true                                            # Debug option for Lambda function


   environment:                                      // Environment Variables set up (examples left in)
 
   environment:                                      # Environment Variables set up (examples left in)
     DOMAIN_SUFFIX: bibble                                         
     DOMAIN_SUFFIX: bibble                                         
     stage: ${self:custom.stage}                    // Stage defined above
     stage: ${self:custom.stage}                    # Stage defined above
     APIKey:  ${self:custom.bibbleSecret.APIKey}    // Held in Secret Credentials
     APIKey:  ${self:custom.bibbleSecret.APIKey}    # Held in Secret Credentials
     Username: ${self:custom.bibbleSecret.Username}  // Held in Secret Credentials  
     Username: ${self:custom.bibbleSecret.Username}  # Held in Secret Credentials  
     Password: ${self:custom.bibbleSecret.Password}  // Held in Secret Credentials       
     Password: ${self:custom.bibbleSecret.Password}  # Held in Secret Credentials       
 
      
      
   deploymentBucket:                                      // Bucket to hold the code
   deploymentBucket:                                      # Bucket to hold the code
     name: mybucketname-${opt:stage, self:provider.stage} // Bucket name
     name: mybucketname-${opt:stage, self:provider.stage} # Bucket name
 


   httpApi:                                              // Define the httpApi   
   httpApi:                                              # Define the httpApi   
     payload: '2.0'                                      // Version  
     payload: '2.0'                                      # Version  
     authorizers:                                        // Authorizer   
     authorizers:                                        # Authorizer   
       accessTokenAzure:                                  // Name   
       accessTokenAzure:                                  # Name   
         identitySource: $request.header.Authorization    // Where to find the Authorization
         identitySource: $request.header.Authorization    # Where to find the Authorization
         issuerUrl: https://sts.windows.net/xxxxxx        // This is an App Registration with a Directory (Tenant) Id which matches in Azure  
         issuerUrl: https://sts.windows.net/xxxxxx        # This is an App Registration with a Directory (Tenant) Id which matches in Azure  
         audience:
         audience:
           - ${self:custom.audience.${self:custom.stage}}
           - ${self:custom.audience.${self:custom.stage}}


functions:
functions:
   myfunction-list:                                    // Function to execute                                   
   myfunction-list:                                    # Function to execute                                   
     handler: ./request/myfunctionlist.handler          // Handler in defined
     handler: ./request/myfunctionlist.handler          # Handler in defined
     timeout: 28                                        // Timeout for handler  
     timeout: 28                                        # Timeout for handler  
     vpc:
     vpc:
       securityGroupIds:
       securityGroupIds:
         - !GetAtt lambdaSecurityGroup.GroupId          // Defined in Resources
         - !GetAtt lambdaSecurityGroup.GroupId          # Defined in Resources
       subnetIds:                                       
       subnetIds:                                       
         - ${self:custom.subnetId.${self:custom.stage}} // vpc Subnet defined under custom
         - ${self:custom.subnetId.${self:custom.stage}} # vpc Subnet defined under custom
     events:                                            // Triggers for this API
     events:                                            # Triggers for this API
       - httpApi:                                         
       - httpApi:                                         
           method: GET                                  // Method
           method: GET                                  # Method
           path: /myfunctionlist                        // Endpoint
           path: /myfunctionlist                        # Endpoint
           authorizer:                                  // Authorizer   
           authorizer:                                  # Authorizer   
             name: accessTokenAzure                    // Defined under httpApi
             name: accessTokenAzure                    # Defined under httpApi


resources:
resources:
Line 315: Line 320:
   Resources:
   Resources:
     lambdaSecurityGroup:
     lambdaSecurityGroup:
       Type: "AWS::EC2::SecurityGroup"                            // This is a definition of an EC2 Security group     
       Type: "AWS::EC2::SecurityGroup"                            # This is a definition of an EC2 Security group     
       Properties:
       Properties:
         GroupName: ${self:service}-${self:provider.stage}-lambda  // This is just name
         GroupName: ${self:service}-${self:provider.stage}-lambda  # This is just name
         GroupDescription: Allow all inbound/outbound              // This is just a description  
         GroupDescription: Allow all inbound/outbound              # This is just a description  
         SecurityGroupIngress:                                    // This adds filtering to the Group on Entering boundary     
         SecurityGroupIngress:                                    # This adds filtering to the Group on Entering boundary     
           - CidrIp: 0.0.0.0/0                                    // Anything entering is ok
           - CidrIp: 0.0.0.0/0                                    # Anything entering is ok
             IpProtocol: '-1'                                      // All protocols
             IpProtocol: '-1'                                      # All protocols
         SecurityGroupEgress:                                      // This adds filtering to the Group on Exiting boundary
         SecurityGroupEgress:                                      # This adds filtering to the Group on Exiting boundary
           - CidrIp: 0.0.0.0/0                                    // Anything leaving is ok     
           - CidrIp: 0.0.0.0/0                                    # Anything leaving is ok     
             IpProtocol: '-1'                                      // All protocols
             IpProtocol: '-1'                                      # All protocols
         VpcId: ${self:custom.vpcId.${self:custom.stage}}          // vpc defined above
         VpcId: ${self:custom.vpcId.${self:custom.stage}}          # vpc defined above
</syntaxhighlight>
</syntaxhighlight>

Latest revision as of 19:29, 22 February 2022

Introduction

This is an example of how to set up a serverless framework function within AWS

Setup

Within AWS there are example templates for each piece of the infrastructure. For this we need to create a

  • S3 Bucket to hold the code
  • IAM::Role to describe the permissions
  • Lambda The function to run
  • Bucket Policy This grants access from the bucket to the lambda function

Templates

Here are the templates I used. I have highlighted where they differ from the provided examples

Stack cowsay-bucket

S3 Bucket

Example can be found here
This needs to be in its own stack and create prior to the other resources.

{
    "Resources": {
        "cowsayBucket": {
            "Type": "AWS::S3::Bucket",
            "Properties": {
                "BucketName": "cowsayBucket"
            }
        }
    }
}

Stack cowsay-lambda

This stack or group of resources holds the lamda, bucket policy and role. The lambda function references the bucket created above.

IAM::Role

Example can be found here

{
    "Resources": {
        "cowsayIamRole": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "Service": [
                                    "lambda.amazonaws.com"
                                ]
                            },
                            "Action": [
                                "sts:AssumeRole"
                            ]
                        }
                    ]
                },
                "Path": "/",
                "Policies": [
                    {
                        "PolicyName": "root",
                        "PolicyDocument": {
                            "Version": "2012-10-17",
                            "Statement": [
                                {
                                    "Effect": "Allow",
                                    "Action": [
                                        "logs:*"
                                    ],
                                    "Resource": "arn:aws:logs:*:*:*"
                                }
                            ]
                        }
                    }
                ]
            }
        }
    }
}

Lambda Function

Example can be found here

{
    "Resources": {
        "cowsayLambda": {
            "Type": "AWS::Lambda::Function",
            "Properties": {
                "Code": {
                    "S3Bucket": "cowsaybucket",
                    "S3Key": "cowsay.zip"
                },
                "Handler": "index.handler",
                "Runtime": "nodejs14.x",
                "Role": {
                    "Fn::GetAtt": [
                        "cowsayIamRole",
                        "Arn"
                    ]
                }
            }
        }
    }
}

Adding the Role to the Lambda

Now we have the base templates we need to add properties to allow them to interact

{
    "Resources": {
        "cowsayBucket": {
            "Type": "AWS::S3::Bucket",
            "Properties": {
                "BucketName": "cowsayBucket"
            }
        },
        "Role": {
            "Ref": "cowsayIamRole"
        }
    }
}

Bucket Policy

We need to allow lambda function access to the bucket. We do this with a the bucket

{
    "Resources": {
        "cowsayBucketPolicy": {
            "Type": "AWS::S3::BucketPolicy",
            "Properties": {
                "Bucket": "cowsaybucket",
                "PolicyDocument": {
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "Service": "serverlessrepo.amazonaws.com"
                            },
                            "Action": "s3:GetObject",
                            "Resource": "arn:aws:s3:::cowsaybucket/*",
                            "Condition": {
                                "StringEquals": {
                                    "aws:SourceArn": {
                                        "Fn::GetAtt": [
                                            "cowsayLambda",
                                            "Arn"
                                        ]
                                    }
                                }
                            }
                        }
                    ]
                }
            }
        }
    }
}

The Code

Do do this we

  • Create a node js package
  • Create a function
  • Create a template to run it locally

Create a node js package

Just a straight forward package

{
  "name": "cowsay",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cowsay": "^1.4.0"
  }
}

Create a function

const cowsay = require('cowsay');
exports.handler = function(event, context) {
    // Get a cow from cowsay!
    let cow = cowsay.say({
        text: "I'm a lambda moooodule",
        e: "oO",
        T: "U "
    });
// Replace line breaks to HTML line breaks
    cow = cow.replace(/[\n|\r|\n\r]/g, '<br />');
// Create the HTML
    var html = '<html><head><title>Cowsay Lambda!</title></head>' + 
        `<body><h1>Welcome to Cowsay Lambda!</h1>${cow}</body></html>`;
    
    // New Source 1
            
    // Return the HTML response
    context.succeed(html);
};

Create a template to run it locally

See here for documentation.

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: An AWS Serverless Specification template describing your function.
Resources:
  myfirstlambda:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: index.handler
      Runtime: nodejs14.x
      CodeUri: .
      Description: ''
      MemorySize: 128
      Timeout: 3

We need to create a function. We create a package and function

Uploading the code

We need to upload the code to the S3Bucket we created above. We need to zip it up making sure that index.js is at the root and not in a folder. Remember you need the code as well. It does not build it in the cloud.

cd <myproject>
zip -r cowsay.zip *

We can upload it by right-clicking lambda and choosing upload lambda

Running the Code

To run the code locally type

sam local invoke

To run the code on AWS right click on the lambda within AWS Explorer in VS Code

Configuration File

service:  myservice                        # name-of-service

custom:                                                                      # These are custom variables which are used throughout the template
  stage: ${opt:stage, self:provider.stage}                                   # Stage
  serverless-offline:                                                        # Configuration for running locally
    noPrependStageInUrl: true                                                # Don't prepend http routes with the stage
    noAuth: true                                                             # Turns off all authorizers
  port: 4033                                                                 # Port to run on
  aws-profile:
    dev:  bibble-nonprod                                                     # Profiles to use Defined in the .aws folder
    prod: bibble-prod
  mysecretvarable: ${ssm:/.../secretsmanager/my-secret-file/my-secret~true}  # Name of secret to retrieve (~true = decrypt)
  vpcId:                                                                     # VPC defined in AWS  
    dev: "vpc-XXX"           
    prod: "vpc-XXX"
  subnetId:                                                                  # VPC subnet defined in AWS (associated with VPC)
    dev: "subnet-XXX"
    prod: "subnet-XXX"
  securityGroupId:                                                           # Security Group define in AWS and associated with VPC and defines firewall
    dev: "sg-XXX"
    prod: "sg-XXX"
  audience:                                                                  # In AWS API Gateway these are defined under the routes -> authorization
    dev: "api://XXX"                                                         # The value is defined in AWS and must match the Azure Registration's Application ID URI
    prod: "api://XXX"


plugins:                # Plugins to run in Serverless Framework
  - serverless-bundle
  - serverless-offline


provider:
  name: aws                                                 # Provider
  stage: dev                                                # Default stage
  profile: ${self:custom.aws-profile.${self:custom.stage}}  # Profile
  runtime: nodejs12.x                                       # Hashing function to use
  lambdaHashingVersion: 20201221                            # Not required anymore
  region: ap-southeast-2                                    # Region to deploy to
  tracing:
    apiGateway: true                                        # Debug option for API  
    lambda: true                                            # Debug option for Lambda function


  environment:                                      # Environment Variables set up (examples left in)
    DOMAIN_SUFFIX: bibble                                        
    stage: ${self:custom.stage}                     # Stage defined above
    APIKey:   ${self:custom.bibbleSecret.APIKey}    # Held in Secret Credentials
    Username: ${self:custom.bibbleSecret.Username}  # Held in Secret Credentials 
    Password: ${self:custom.bibbleSecret.Password}  # Held in Secret Credentials       

    
  deploymentBucket:                                      # Bucket to hold the code
    name: mybucketname-${opt:stage, self:provider.stage} # Bucket name


  httpApi:                                               # Define the httpApi  
    payload: '2.0'                                       # Version 
    authorizers:                                         # Authorizer  
      accessTokenAzure:                                  # Name  
        identitySource: $request.header.Authorization    # Where to find the Authorization
        issuerUrl: https://sts.windows.net/xxxxxx        # This is an App Registration with a Directory (Tenant) Id which matches in Azure 
        audience:
          - ${self:custom.audience.${self:custom.stage}}

functions:
  myfunction-list:                                     # Function to execute                                   
    handler: ./request/myfunctionlist.handler          # Handler in defined
    timeout: 28                                        # Timeout for handler 
    vpc:
      securityGroupIds:
        - !GetAtt lambdaSecurityGroup.GroupId          # Defined in Resources
      subnetIds:                                      
        - ${self:custom.subnetId.${self:custom.stage}} # vpc Subnet defined under custom
    events:                                            # Triggers for this API
      - httpApi:                                        
          method: GET                                  # Method
          path: /myfunctionlist                        # Endpoint
          authorizer:                                  # Authorizer  
            name: accessTokenAzure                     # Defined under httpApi

resources:
  Description: My Function description
  Resources:
    lambdaSecurityGroup:
      Type: "AWS::EC2::SecurityGroup"                             # This is a definition of an EC2 Security group    
      Properties:
        GroupName: ${self:service}-${self:provider.stage}-lambda  # This is just name
        GroupDescription: Allow all inbound/outbound              # This is just a description 
        SecurityGroupIngress:                                     # This adds filtering to the Group on Entering boundary    
          - CidrIp: 0.0.0.0/0                                     # Anything entering is ok
            IpProtocol: '-1'                                      # All protocols
        SecurityGroupEgress:                                      # This adds filtering to the Group on Exiting boundary
          - CidrIp: 0.0.0.0/0                                     # Anything leaving is ok    
            IpProtocol: '-1'                                      # All protocols
        VpcId: ${self:custom.vpcId.${self:custom.stage}}          # vpc defined above