Serverless Plugin Warmup

homepage icon https://github.com/FidelLimited/serverless-plugin-warmup
Follow @FidelLimited

Tracked

NPM Downloads Last Month
44226
Issues
5
Stars
412
Forks
55
Watchers
412
Watch Star Fork Issue Download License NPM Build Status Coverage Status Contributors

Repo README Contents:

Serverless WarmUp Plugin ♨

Serverless npm version npm monthly downloads Build Status Coverage Status Dependency Status license

Keep your lambdas warm during winter.

Requirements:

How it works

WarmUp solves cold starts by creating a scheduled lambda that invokes all the selected service’s lambdas in a configured time interval (default: 5 minutes) and forcing your containers to stay warm.

Installation

Install via npm in the root of your Serverless service:

npm install --save-dev serverless-plugin-warmup

Add the plugin to the plugins array in your Serverless serverless.yaml:

plugins:
  - serverless-plugin-warmup

Configuration

Most options are set under custom.warmup in the serverless.yaml file.

But there are some options which can also be set under custom.warmup to be applied to all lambdas to be warmed up or can be overridden on each individual lambda.

custom:
  warmup:
    enabled: true # Whether to warm up functions by default or not
    folderName: '_warmup' # Name of the folder created for the generated warmup 
    cleanFolder: false
    memorySize: 256
    name: 'make-them-pop'
    role: myCustRole0
    tags:
      Project: foo
      Owner: bar 
    vpc: false
    events:
      - schedule: 'cron(0/5 8-17 ? * MON-FRI *)' # Run WarmUp every 5 minutes Mon-Fri between 8:00am and 5:55pm (UTC)
    package:
      individually: true
      exclude: # exclude additional binaries that are included at the serverless package level
        - ../**
        - ../../**
      include:
        - ./**
    timeout: 20
    prewarm: true # Run WarmUp immediately after a deploymentlambda
    payload: 
      source: my-custom-source
      other: 20
    payloadRaw: true # Won't JSON.stringify() the payload, may be necessary for Go/AppSync deployments
    concurrency: 5 # Warm up 5 concurrent instances
    
functions:
  myColdfunction:
    handler: 'myColdfunction.handler'
    events:
      - http:
          path: my-cold-function
          method: post
    warmup:
      enabled: false

  myLowConcurrencyFunction:
    handler: 'myLowConcurrencyFunction.handler'
    events:
      - http:
          path: my-low-concurrency-function
          method: post
    warmup:
      payload: different-source-only-for-this-lambda
      concurrency: 1
   
  myProductionOnlyFunction:
    handler: 'myProductionOnlyFunction.handler'
    events:
      - http:
          path: my-production-only-function
          method: post
    warmup:
      enabled: prod
      
   myDevAndStagingOnlyFunction:
    handler: 'myDevAndStagingOnlyFunction.handler'
    events:
      - http:
          path: my-dev-and-staging-only-function
          method: post
    warmup:
      enabled:
        - dev
        - staging
Options should be tweaked depending on:

Legacy options

Over time some options have been removed form the pluging. For now, we keep backwards compatibility so they still work. However, they are listed here only to facilitate upgrading the pluging and we strongly recommend switching to the options defined above as soon as possible.

Permissions

WarmUp requires some permissions to be able to invoke your lambdas.

custom:
  warmup:
    folderName: '_warmup' # Name of the folder created for the generated warmup 
    cleanFolder: false
    memorySize: 256
    name: 'make-them-pop'
    role:  myCustRole0
    events:
      - schedule: 'cron(0/5 8-17 ? * MON-FRI *)' # Run WarmUp every 5 minutes Mon-Fri between 8:00am and 5:55pm (UTC)
    timeout: 20
    prewarm: true # Run WarmUp immediately after a deployment
    tags:
      Project: foo
      Owner: bar

.....

resources:
  Resources:
    myCustRole0:
      Type: AWS::IAM::Role
      Properties:
        Path: /my/cust/path/
        RoleName: MyCustRole0
        AssumeRolePolicyDocument:
          Version: '2017'
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
              Action: sts:AssumeRole
        Policies:
          - PolicyName: myPolicyName
            PolicyDocument:
              Version: '2017'
              Statement:
                - Effect: Allow # WarmUp lamda to send logs to CloudWatch
                  Action:
                    - logs:CreateLogGroup
                    - logs:CreateLogStream
                    - logs:PutLogEvents
                  Resource: 
                    - 'Fn::Join':
                      - ':'
                      -
                        - 'arn:aws:logs'
                        - Ref: 'AWS::Region'
                        - Ref: 'AWS::AccountId'
                        - 'log-group:/aws/lambda/*:*:*'
                - Effect: Allow # WarmUp lamda to manage ENIS (only needed if deploying to VPC, https://docs.aws.amazon.com/lambda/latest/dg/vpc.html)
                  Action:
                    - ec2:CreateNetworkInterface
                    - ec2:DescribeNetworkInterfaces
                    - ec2:DetachNetworkInterface
                    - ec2:DeleteNetworkInterface
                  Resource: "*"
                - Effect: 'Allow' # WarmUp lamda to invoke the functions to be warmed
                  Action:
                    - 'lambda:InvokeFunction'
                  Resource:
                  - Fn::Join:
                    - ':'
                    - - arn:aws:lambda
                      - Ref: AWS::Region
                      - Ref: AWS::AccountId
                      - function:${self:service}-${opt:stage, self:provider.stage}-*

The permissions can also be added to all lambdas using iamRoleStatements under provider (see https://serverless.com/framework/docs/providers/aws/guide/functions/#permissions):

provider:
  name: aws
  runtime: nodejs6.10
  iamRoleStatements:
    - Effect: 'Allow'
      Action:
        - 'lambda:InvokeFunction'
      Resource:
      - Fn::Join:
        - ':'
        - - arn:aws:lambda
          - Ref: AWS::Region
          - Ref: AWS::AccountId
          - function:${self:service}-${opt:stage, self:provider.stage}-*

If using pre-warm, the deployment user also needs a similar policy so it can run the WarmUp lambda.

On the function side

Lambdas invoked by WarmUp will have the event source serverless-plugin-warmup (unless otherwise specified using the payload option):

{
  "Event": {
    "source": "serverless-plugin-warmup"
  }
}

To minimize cost and avoid running your lambda unnecessarily, you should add an early return call before your lambda logic when that payload is received.

// Using the Promise style
module.exports.lambdaToWarm = async function(event, context) {
  /** Immediate response for WarmUp plugin */
  if (event.source === 'serverless-plugin-warmup') {
    console.log('WarmUp - Lambda is warm!');
    return 'Lambda is warm!';
  }

  ... add lambda logic after
}

// Using the Callback style
module.exports.lambdaToWarm = function(event, context, callback) {
  /** Immediate response for WarmUp plugin */
  if (event.source === 'serverless-plugin-warmup') {
    console.log('WarmUp - Lambda is warm!')
    return callback(null, 'Lambda is warm!')
  }

  ... add lambda logic after
}

// Using context.
// This could be useful if you are handling the raw input and output streams.
module.exports.lambdaToWarm = async function(event, context) {
  /** Immediate response for WarmUp plugin */
  if (context.custom.source === 'serverless-plugin-warmup') {
    console.log('WarmUp - Lambda is warm!');
    return 'Lambda is warm!';
  }

  ... add lambda logic after
}

If you’re using the concurrency option you might want to add a slight delay before returning on warmup calls to ensure that your function doesn’t return before all concurrent requests have been started:

module.exports.lambdaToWarm = async (event, context) => {
  if (event.source === 'serverless-plugin-warmup') {
    console.log('WarmUp - Lambda is warm!');
    /** Slightly delayed (25ms) response 
    	to ensure concurrent invocation */
    await new Promise(r => setTimeout(r, 25));
    return 'Lambda is warm!';
    
  }

  ... add lambda logic after
}

Deployment

WarmUp supports serverless deploy.

Packaging

WarmUp supports serverless package.

By default, the WarmUp function is packaged individually and it uses a folder named _warmup to store duiring the packaging process, which is deleted at the end of the process.

If you are doing your own package artifact you can set the cleanFolder option to false and include the _warmup folder in your custom artifact.

Gotchas

The WarmUp function use normal calls to the AWS SDK in order to keep your lambdas warm. By deafult, the WarmUp function is deployed outside of any VPC so it can reach AWS API. If you use the VPC option to deploy your WarmUp function to a VPC subnet it will need internet access. You can do it by using an Internet Gateway or a Network Address Translation (NAT) gateway.

Cost

You can check the Lambda pricing and CloudWatch pricing or can use the AWS Lambda Pricing Calculator to estimate the monthly cost

Example

If you want to warm 10 functions, each with memorySize = 1024 and duration = 10, using the default settings (and we ignore the free tier):

CloudWatch costs are not in this example because they are very low.

Contribute

Help us making this plugin better and future proof.

License

This software is released under the MIT license. See the license file for more details.