I have a Python Lambda and since I started using AWS X-Ray the package size has ballooned from 445KB to 9.5MB.
To address this and speed up deployments of my code, I have packaged my requirements separately and added a layer to my template. The documentation suggests that this approach should work.
Packaging dependencies in a layer reduces the size of the deployment package that you upload when you modify your code.
pip install --target ../package/python -r requirements.txt
Resources:
...
ProxyFunction:
Type: AWS::Serverless::Function
Properties:
Architectures:
- x86_64
CodeUri: proxy/
Handler: app.lambda_handler
Layers:
- !Ref ProxyFunctionLibraries
Role: !GetAtt ProxyFunctionRole.Arn
Runtime: python3.8
Tracing: Active
ProxyFunctionLibraries:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: proxy-function-lib
Description: Dependencies for the ProxyFunction.
ContentUri: package/.
CompatibleRuntimes:
- python3.8
However, this doesn't seem to have prevented the Lambda from still packaging everything in the top layer, and every time I deploy the package is still 9.5MB. The new layer for some reason is 11MB in size, but that is only being deployed when a change is made.
How can I reduce the size of the Lambda function package?
Actually the solution here was quite simple, although not obvious to non-Lambda experts.
As described in the question, the first step was to build the package library.
pip install --target ../package/python -r requirements.txt
However, when building the Lambda using sam build -u the same 'requirements.txt' file is used and the required dependencies were again being installed, this time as part of the app.
So all I had to do was remove the requirements that I wish packaged in a separate layer and rebuild. It does mean that I have to maintain 2x 'requirements.txt' but that is entirely manageable.
I've opened an issue and hopefully AWS will update their documentation.
I am struggling with the same problem and solved it differently. Leaving this answer as a note to future question-seekers.
My current lambda structure is the following:
├── events
│ └── event.json
├── ingress
│ ├── app.py # lambda code
│ └── __init__.py
├── __init__.py
├── lib_layer # contains self written helpers and python dependencies
│ ├── helper.py
│ └── requirements.txt
├── samconfig.toml
├── template.yaml
└── tests
├── ...
In my template.yaml I have the following code snippets:
SQSIngestion:
Type: AWS::Serverless::Function
Properties:
CodeUri: ingress/
Handler: app.lambda_handler
Runtime: python3.9
Layers:
- !Ref PythonLibLayer
Architectures:
- x86_64
PythonLibLayer:
Type: AWS::Serverless::LayerVersion
Properties:
ContentUri: lib_layer
CompatibleRuntimes:
- python3.9
Metadata:
BuildMethod: python3.9
Since the Lambda Function already has the layer containing all dependencies + my helpers attached, the requirements.txt in /ingress can be omitted but works nevertheless when invoking the function in AWS. By calling
sam build will automatically build the dependency layer and ignore the function folder for building the dependencies.
As a tip, if working with VSCode, add in your settings.json the following line to fix pylances import error for self written packages.
{
"python.analysis.extraPaths": ["lib_layer"]
}
Related
I'm trying to deploy a basic serverless application that contains two Rust lambda functions. I'm using SAM to deploy the application.
The issue is how to get SAM to pick up the correct "bootstrap" file. Because both functions are built in the same CodeUri path, SAM does not execute both the Make commands. Instead, it just copies the output of Function1 to Function2 (this seems like a design flaw in SAM?). Thus, both lambdas currently get deployed with the same code.
My build directory is
myapp/
- src/
- bin/
- function1.rs (note: function1 & 2 depend on lib.rs)
- function2.rs
- lib.rs
- Cargo.toml
- Makefile
- template.yaml
The template.yaml file:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Handler: bootstrap.is.the.handler
Runtime: provided.al2
Architectures:
- x86_64
Resources:
Function1:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
Function2:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
The Makefile is:
build-Function1:
cargo lambda build
cp ./target/lambda/function1/bootstrap $(ARTIFACTS_DIR)
build-Function2: # This never gets run!
cargo lambda build
cp ./target/lambda/function2/bootstrap $(ARTIFACTS_DIR)
Commands to build/deploy
sam build
sam deploy
I'm open to other build structures. I've also tried structuring the project using rust workspaces. But, because SAM copies the build source to a separate directory, I cannot find a way to add module dependencies.
After much struggle, I have come up with a hacky solution, that I'm sure cannot be the recommended way.
Use rust workspaces, so the folder structure is:
root/
common/
lib.rs
Cargo.toml
function1/
main.rs
Cargo.toml
Makefile
function2/
main.rs
Cargo.toml
Makefile
Cargo.toml
template.yaml
root/Cargo.toml:
[workspace]
members = [
"common"
"function1",
"function2",
]
Set template.yaml file to use different codeURIs:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Handler: bootstrap.is.the.handler
Runtime: provided.al2
Architectures:
- x86_64
Resources:
Function1:
Type: AWS::Serverless::Function
Properties:
CodeUri: function1
Function2:
Type: AWS::Serverless::Function
Properties:
CodeUri: function2
(The hack step) In each Makefile, cp back into the source dir so it builds with all the source. (This has added benefit of sharing the build cache between targets)
build-Function1:
cd $(PWD); cargo lambda build --release
cp $(PWD)/target/lambda/function1/bootstrap $(ARTIFACTS_DIR)
This solution is compatible with cargo lambda watch and sam build/sam deploy commands.
Binary size is large since all lambdas duplicate the base library. I'm uncertain if this is avoidable with Rust.
I'm yet to trial it with deployment from CI servers.
I have tried to upload my application using servless/lambda function AWS, but i got this issue:
An error occurred: AppLambdaFunction - Unzipped size must be smaller than 262144000 bytes (Service: AWSLambdaInternal; Status Code: 400; Error Code: InvalidParameterValueException; Request ID: 8ea0d887-5743-4db1-96cd-6c5efa57b081).
What is the best way to resolve it?
Look my dependencies:
"dependencies": {
"ethereumjs-tx": "^1.3.7",
"aws-sdk": "^2.4.52",
"body-parser": "^1.18.3",
"compression": "^1.7.4",
"consign": "^0.1.6",
"cors": "^2.8.5",
"express": "^4.16.4",
"helmet": "^3.16.0",
"moment": "^2.24.0",
"openzeppelin-solidity": "^2.3.0",
"serverless": "^1.48.2",
"serverless-http": "^1.9.1",
"serverless-offline": "^4.9.4",
"truffle": "^5.1.9",
"truffle-hdwallet-provider": "^1.0.17",
"web3": "^1.2.5-rc.0"
},
Serverless.yml:
provider:
name: aws
runtime: nodejs8.10
stage: v1
region: us-east-1
timeout: 30
memorySize: 512
package:
excludeDevDependencies: true
exclude:
- .git/**
- .vscode/**
- venv/**
functions:
app:
handler: handler.run
events:
- http:
path: /
method: ANY
cors: true
- http:
path: /{proxy+}
method: ANY
cors: true
plugins:
- serverless-offline
Use the directive exclude at your serverless.yml file. In case of Python, I've been used it as follows:
package:
exclude:
- node_modules/**
- venv/**
The build process will exclude them from the build before sending to AWS.
Tip I got in this issue at Github. The documentation for this directive is detailed here.
You can use module bundlers to package the code.
Using module bundlers such as webpack
You can consider using plugins like serverless-webpack. The serverless-webpack plugin is using webpack to build the project and it will only include the bare minimum files required to run your application. It will not include the entire node_modules directory. so that your deployment package will be smaller.
a note about using of Lambda layers
Like others mentioned, you can use the layers and move some of the libraries and code to the layer. Layers are mainly used to share code between functions. The unzipped deployed package including layers cannot exceed 250MB.
hope this helps.
References:
https://github.com/serverless-heaven/serverless-webpack
https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html#configuration-layers-path
I've had success resolving this error message using the serverless-esbuild plugin, and configuring it as follows in serverless.yml:
service: service_name
frameworkVersion: '3'
provider:
name: aws
runtime: nodejs12.x
plugins:
- serverless-esbuild
custom:
esbuild:
bundle: true
minify: false
sourcemap: true
exclude: 'aws-sdk'
target: node14
define:
'require.resolve': undefined
platform: node
concurrency: 10
You can load larger packages into AWS Lambda indirectly using s3:
Load your package into a bucket/key on S3
In the Lambda console choose Function Code -> Code Entry Type -> Upload a file from S3
See my answer here
You can deploy a Lambda function using a Docker image and it bypasses this problem, allowing a function with its dependencies to be as large as 10 gb.
Adding exclude under package is deprecated. We can use pattern to remove node_modules.
Example to remove files in the serverless.yml
...remaining props
package:
patterns:
- '!.git/**'
- '!test/**'
- '!e2e/**'
- '!src/**'
- '!node_modules/**'
Deprecation for exclude and
Pattern
Recently i faced the same issue, my total package size was more than 40mbs and it was also including the venv (python virtual environment) folder that resides in the project directory. I excluded it and build size got reduced to 16 mbs. and the project was deployed successfully. I added the following in serverless.yaml
package:
patterns:
- '!node_modules/**'
- '!venv/**'
- '!apienv/**'
- '!__pycache__/**'
So I uploaded this layer to AWS using the Serverless framework:
service: webstorm-layer
provider:
name: aws
runtime: nodejs8.10
region: us-east-1
layers:
nodejs:
path: nodejs # path to contents on disk
name: node-webstormlibs # optional, Deployed Lambda layer name
description: JS shared libs for node
compatibleRuntimes:
- nodejs8.10
allowedAccounts:
- '*'
The libraries I need are inside the "nodejs" directory, there lays my packages.json file and all the "node_modules" directories. So far all looks fine, but when I try to run a lambda that uses the "node-webstormlibs" layer, I'm getting the message:
"errorMessage": "Cannot find module 'pg'",
The pg module actually exists in the zip file that creates the layer. Then, I have doubts about how to import a module that is inside the layer. In some tutorials I see:
import pg from "pg";
like always, but in other I see:
import pg from "/opt/pg";
or even:
import pg from "/opt/nodejs/node_modules/pg";
I don't know if the "path:" option in my serverless.yml is correct though.
In the server the path is:
NODE_PATH=/opt/nodejs/node8/node_modules/:/opt/nodejs/node_modules:$LAMBDA_RUNTIME_DIR/node_modules
UPDATE
Putting all in the dir /nodejs/node8, made the trick.
I have the following project tree
Where nodejs folder is a lambda layer defined in the following serverless.yaml
service: aws-nodejs # NOTE: update this with your service name
provider:
name: aws
runtime: nodejs8.10
stage: dev
plugins:
- serverless-offline
layers:
layer1:
path: nodejs # required, path to layer contents on disk
name: ${self:provider.stage}-layerName # optional, Deployed Lambda layer name
functions:
hello:
handler: handler.hello
layers:
- {Ref: Layer1LambdaLayer}
events:
- http:
path: /dev
method: get
The layer1 only contains UUID package.
So when I try to run the lambda locally using serverless offline plugin, it says can't find module UUID.
But when I deploy the code to AWS, it run like a charm.
Any way we can get lambda layers running locally for testing purpose? and for speeding up the development?
Or is there any way where I can dynamically set the node_module path to point to the layer folder during the development and once I need to push to production, it change the path to the proper one
Ok after many trials, I figure out a working solution
I added a npm run command which export a temporary node_module path to the list of paths
"scripts": {
"offline": "export NODE_PATH=\"${PWD}/nodejs/node_modules\" && serverless offline"
},
So, node can lookup for the node modules inside the sub folders
I got around this by running serverless-offline in a container and copying my layers into the /opt/ directory with gulp. I set a gulp watch to monitor any layer changes and to copy them to the /opt/ directory.
I use layers in serverless offline via installing a layer from local file system as a dev dependency.
npm i <local_path_to_my_layer_package> --save-dev
BTW this issue was fixed in sls 1.49.0.
Just run:
sudo npm i serverless
Then you should specify package include in serverless.yml's layer section
service: aws-nodejs # NOTE: update this with your service name
provider:
name: aws
runtime: nodejs8.10
stage: dev
plugins:
- serverless-offline
layers:
layer1:
path: nodejs # required, path to layer contents on disk
package:
include:
- node_modules/**
name: ${self:provider.stage}-layerName # optional, Deployed Lambda layer name
functions:
hello:
handler: handler.hello
layers:
- {Ref: Layer1LambdaLayer}
events:
- http:
path: /dev
method: get
Tested on nodejs10.x runtime
I am building a Python deployment package for AWS Lambda that relies on dlib. dlib has OS dependencies and it relies on cmake in order to build out the binaries. I am wondering how to do this given that I have a Mac and am doing my development on that environment. I am aware of Docker but I am not sure how to setup an image to compile the binaries for AWS. Any help in doing this would be appreciated.
The easiest way is to use the plugin
serverless-package-python-functions
So simply define in the serverless.yml
package:
individually: true
custom:
pkgPyFuncs:
buildDir: _build
requirementsFile: requirements.txt
cleanup: true
useDocker: true
Important is to use useDocker: true - this is spinning up a docker (locally) based on the AWS AMI - therefore you get the right dependencies.
After that create your function in serverless.yml:
functions:
test:
name: ${opt:stage, self:provider.stage}-${self:service}-test
handler: lambda_function.lambda_handler
package:
include:
- ./test
artifact: ${self:custom.pkgPyFuncs.buildDir}/${self:functions.test.name}.zip
Inside your test-folder place the requirements.txt. This file will be used for deploying the service with the right packages.
let me know if you have further questions