AWS Serverless: Difference between revisions
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 | |||
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 | name: aws # Provider | ||
stage: dev | stage: dev # Default stage | ||
profile: ${self:custom.aws-profile.${self:custom.stage}} | profile: ${self:custom.aws-profile.${self:custom.stage}} # Profile | ||
runtime: nodejs12.x | runtime: nodejs12.x # Hashing function to use | ||
lambdaHashingVersion: 20201221 | lambdaHashingVersion: 20201221 # Not required anymore | ||
region: ap-southeast-2 | region: ap-southeast-2 # Region to deploy to | ||
tracing: | tracing: | ||
apiGateway: true | apiGateway: true # Debug option for API | ||
lambda: true | lambda: true # Debug option for Lambda function | ||
environment: | |||
environment: # Environment Variables set up (examples left in) | |||
DOMAIN_SUFFIX: bibble | DOMAIN_SUFFIX: bibble | ||
stage: ${self:custom.stage} | stage: ${self:custom.stage} # Stage defined above | ||
APIKey: ${self:custom.bibbleSecret.APIKey} | APIKey: ${self:custom.bibbleSecret.APIKey} # Held in Secret Credentials | ||
Username: ${self:custom.bibbleSecret.Username} | Username: ${self:custom.bibbleSecret.Username} # Held in Secret Credentials | ||
Password: ${self:custom.bibbleSecret.Password} | Password: ${self:custom.bibbleSecret.Password} # Held in Secret Credentials | ||
deploymentBucket: | deploymentBucket: # Bucket to hold the code | ||
name: mybucketname-${opt:stage, self:provider.stage} | name: mybucketname-${opt:stage, self:provider.stage} # Bucket name | ||
httpApi: | httpApi: # Define the httpApi | ||
payload: '2.0' | payload: '2.0' # Version | ||
authorizers: | authorizers: # Authorizer | ||
accessTokenAzure: | accessTokenAzure: # Name | ||
identitySource: $request.header.Authorization | identitySource: $request.header.Authorization # Where to find the Authorization | ||
issuerUrl: https://sts.windows.net/xxxxxx | 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: | myfunction-list: # Function to execute | ||
handler: ./request/myfunctionlist.handler | handler: ./request/myfunctionlist.handler # Handler in defined | ||
timeout: 28 | timeout: 28 # Timeout for handler | ||
vpc: | vpc: | ||
securityGroupIds: | securityGroupIds: | ||
- !GetAtt lambdaSecurityGroup.GroupId | - !GetAtt lambdaSecurityGroup.GroupId # Defined in Resources | ||
subnetIds: | subnetIds: | ||
- ${self:custom.subnetId.${self:custom.stage}} | - ${self:custom.subnetId.${self:custom.stage}} # vpc Subnet defined under custom | ||
events: | events: # Triggers for this API | ||
- httpApi: | - httpApi: | ||
method: GET | method: GET # Method | ||
path: /myfunctionlist | path: /myfunctionlist # Endpoint | ||
authorizer: | authorizer: # Authorizer | ||
name: accessTokenAzure | name: accessTokenAzure # Defined under httpApi | ||
resources: | resources: | ||
Line 315: | Line 320: | ||
Resources: | Resources: | ||
lambdaSecurityGroup: | lambdaSecurityGroup: | ||
Type: "AWS::EC2::SecurityGroup" | Type: "AWS::EC2::SecurityGroup" # This is a definition of an EC2 Security group | ||
Properties: | Properties: | ||
GroupName: ${self:service}-${self:provider.stage}-lambda | GroupName: ${self:service}-${self:provider.stage}-lambda # This is just name | ||
GroupDescription: Allow all inbound/outbound | GroupDescription: Allow all inbound/outbound # This is just a description | ||
SecurityGroupIngress: | SecurityGroupIngress: # This adds filtering to the Group on Entering boundary | ||
- CidrIp: 0.0.0.0/0 | - CidrIp: 0.0.0.0/0 # Anything entering is ok | ||
IpProtocol: '-1' | IpProtocol: '-1' # All protocols | ||
SecurityGroupEgress: | SecurityGroupEgress: # This adds filtering to the Group on Exiting boundary | ||
- CidrIp: 0.0.0.0/0 | - CidrIp: 0.0.0.0/0 # Anything leaving is ok | ||
IpProtocol: '-1' | IpProtocol: '-1' # All protocols | ||
VpcId: ${self:custom.vpcId.${self:custom.stage}} | 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