Serverless Development and Deployment Environment for Python 3.6 and 3.7
1.3K
The arvslat/lambda-env:python3.6 builds on top of lambci/lambda:build-python3.6 to allow rapid development and deployment of complex AWS Lambda functions which require package optimization and custom deployment steps. You'll most likely encounter different types of issues and blockers trying to build and develop your AWS Lambda functions once your application starts to involve more complex requirements and heavy libraries. This environment allows users to work inside an isolated container environment with several shortcuts and scripts as long as the required folder structure and steps are followed.
/var/task and the lambda.cli tool assumes that you are running it inside that directoryarvslat/lambda-env:python3.6 is an upgraded version of lambci/lambda:build-python3.6 that includes lambda.cli and other tools and scripts you'll need to develop, maintain, and deploy your AWS Lambda Functionslambda.cli initarvslat/lambda-env makes use of AWS CLI and AWS SAM internally to manage your resources and functionsRun Container and mount your directory
Activate Environment using source lambda-environment
Initialize Project using lambda.cli init
Configure AWS Credentials using aws configure
Configure Project Configuration and Environments using lambda.cli configure
Build Project and Install Dependencies using lambda.cli build
Package Project using lambda.cli package
Test Project using lambda.cli test
Deploy Project using lambda.cli deploy
Invoke Deployed Function using lambda.cli invoke
Debug Deployment using lambda.cli debug-deployment
To get started with a new project or application, create an empty directory, mount that directory inside the container, and initialize with lambda.cli init
$ cd /Projects/sample-application-01
$ docker run --rm -it -v "$PWD":/var/task arvslat/lambda-env:python3.6 bash
bash-4.2# source lambda-environment
(venv) [07:47:31] /var/task # lambda.cli init
... (and so on) ...
.
├── app
│ └── index.py
├── bin
├── config
│ ├── env.development.yml
│ ├── env.production.yml
│ ├── env.staging.yml
│ ├── requirements.txt
│ └── sam.template.yml
├── README.md
├── scripts
│ ├── install.sh
│ └── optimize.sh
├── tests
│ └── events
├── tmp
└── venv
├── bin
├── include
├── lib
├── lib64 -> lib
└── pyvenv.cfg
$ docker run --rm -it -v "$PWD":/var/task arvslat/lambda-env:python3.6 bash
# Example: Memory = 4GB
$ docker run -m=4g --rm -it -v "$PWD":/var/task arvslat/lambda-env:python3.6 bash
bash-4.2# source lambda-environment
(venv) [00:00:00] /var/task # lambda-install-packages
(venv) [00:00:00] /var/task # lambda-check-package-size
(venv) [00:00:00] /var/task # lambda-optimize-package-size
(venv) [00:00:00] /var/task # lambda-check-package-size
(venv) [00:00:00] /var/task # lambda-generate-zip
(venv) [00:00:00] /var/task # lambda-check-package-size
(venv) [00:00:00] /var/task # aws configure
(venv) [00:00:00] /var/task # sam package --template-file config/sam.template.yml --output-template-file tmp/sam.package.yml --s3-bucket <bucket>
(venv) [00:00:00] /var/task # aws s3 cp /tmp/app.zip `lambda-get-s3-package-location`
(venv) [00:00:00] /var/task # sam deploy --template-file tmp/sam.package.yml --stack-name <stack name> --capabilities CAPABILITY_IAM
lambda.cli [Version 2 - Preferred]bash-4.2# source lambda-environment
(venv) [00:00:00] /var/task # aws configure
(venv) [00:00:00] /var/task # lambda.cli init
(venv) [00:00:00] /var/task # lambda.cli configure
(venv) [00:00:00] /var/task # lambda.cli build
(venv) [00:00:00] /var/task # lambda.cli test --event event
(venv) [00:00:00] /var/task # lambda.cli package
(venv) [00:00:00] /var/task # lambda.cli deploy
(venv) [00:00:00] /var/task # lambda.cli check-deployment
(venv) [00:00:00] /var/task # lambda.cli invoke
lambda.cli initCURRENT_WORKING_DIRECTORY = /var/task
RUNNING INSIDE VENV = True
COMMAND = init
CONTEXT, PARAMETERS, and DEFAULT VALUES
{
"app_name": "hello_world",
"command": "init",
"environment": "development",
"test_event": "event",
"version": false
}
EXECUTE COMMAND [init]
[+] Creating directory [bin]
[+] Creating directory [tests]
[+] Creating directory [tests/events]
[+] Creating directory [config]
[+] Creating directory [app]
[+] Creating directory [tmp]
[+] Creating directory [scripts]
[+] Creating empty file [README.md]
[+] Creating empty file [config/requirements.txt]
[+] Creating empty file [scripts/install.sh]
[+] Creating empty file [scripts/optimize.sh]
[+] Creating empty file [tmp/.gitkeep]
[+] Creating empty file [bin/.gitkeep]
[+] Creating empty file [tests/events/.gitkeep]
[+] Loading contents of [/root/lambda_cli/templates/env.development.yml]: START
[+] Loading contents of [/root/lambda_cli/templates/env.development.yml]: END
[+] Writing contents to [config/env.development.yml]: START
[+] Writing contents to [config/env.development.yml]: DONE
[+] Loading contents of [/root/lambda_cli/templates/event.json]: START
[+] Loading contents of [/root/lambda_cli/templates/event.json]: END
[+] Writing contents to [tests/events/event.json]: START
[+] Writing contents to [tests/events/event.json]: DONE
[+] Loading contents of [/root/lambda_cli/templates/env.staging.yml]: START
[+] Loading contents of [/root/lambda_cli/templates/env.staging.yml]: END
[+] Writing contents to [config/env.staging.yml]: START
[+] Writing contents to [config/env.staging.yml]: DONE
[+] Loading contents of [/root/lambda_cli/templates/env.production.yml]: START
[+] Loading contents of [/root/lambda_cli/templates/env.production.yml]: END
[+] Writing contents to [config/env.production.yml]: START
[+] Writing contents to [config/env.production.yml]: DONE
[+] Loading contents of [/root/lambda_cli/templates/sam.template.yml]: START
[+] Loading contents of [/root/lambda_cli/templates/sam.template.yml]: END
[+] Writing contents to [config/sam.template.yml]: START
[+] Writing contents to [config/sam.template.yml]: DONE
[+] Loading contents of [/root/lambda_cli/templates/.gitignore]: START
[+] Loading contents of [/root/lambda_cli/templates/.gitignore]: END
[+] Writing contents to [.gitignore]: START
[+] Writing contents to [.gitignore]: DONE
[+] Loading contents of [/root/lambda_cli/templates/index.py]: START
[+] Loading contents of [/root/lambda_cli/templates/index.py]: END
[+] Writing contents to [app/index.py]: START
[+] Writing contents to [app/index.py]: DONE
TREE of /var/task
[+] Executing [tree -L 2]
.
├── app
│ └── index.py
├── bin
├── config
│ ├── env.development.yml
│ ├── env.production.yml
│ ├── env.staging.yml
│ ├── requirements.txt
│ └── sam.template.yml
├── README.md
├── scripts
│ ├── install.sh
│ └── optimize.sh
├── tests
│ └── events
├── tmp
└── venv
├── bin
├── include
├── lib
├── lib64 -> lib
└── pyvenv.cfg
env.development.ymlNOTE: Update this file with your custom configuration details. Run lambda.cli configure to check if your configuration is valid.
resources:
function:
label: 'TargetFunction'
name: '<Unique Lambda Function Name per Region>'
app:
stack_name: '<Unique CloudFormation Stack Name per Region>'
region: '<us-east-1 / us-east-2 / ...>'
s3_bucket: '<S3 Bucket name located in the same region of the Lambda Function>'
lambda.cli configure(venv) [07:48:18] /var/task # lambda.cli configure
CURRENT_WORKING_DIRECTORY = /var/task
RUNNING INSIDE VENV = True
COMMAND = configure
CONTEXT, PARAMETERS, and DEFAULT VALUES
{
"app_name": "hello_world",
"command": "configure",
"environment": "development",
"test_event": "event",
"version": false
}
EXECUTE COMMAND [configure]
CONFIGURATION CHECKS [config/env.development.yml]
[+] resources.function.label = TargetFunction
[+] resources.function.name = TargetFunctionDevelopmentZZ
[+] app.stack_name = lambda-env-stack-dev-01
[+] app.region = us-east-1
[+] app.s3_bucket = sample-sam-bucket-12345
GENERATE SAM CONFIG FILE
[+] Loading contents of [config/sam.template.yml]: START
[+] Loading contents of [config/sam.template.yml]: END
[+] Writing contents to [tmp/sam.development.yml]: START
[+] Writing contents to [tmp/sam.development.yml]: DONE
[+] Generated [tmp/sam.development.yml]
COMPLETED EXECUTION [configure]
lambda.cli buildCURRENT_WORKING_DIRECTORY = /var/task
RUNNING INSIDE VENV = True
COMMAND = build
CONTEXT, PARAMETERS, and DEFAULT VALUES
{
"app_name": "hello_world",
"command": "build",
"environment": "development",
"test_event": "event",
"version": false
}
EXECUTE COMMAND [build]
CONFIGURATION CHECKS [config/env.development.yml]
[+] resources.function.label = TargetFunction
[+] resources.function.name = TargetFunctionDevelopmentZZ
[+] app.stack_name = lambda-env-stack-dev-01
[+] app.region = us-east-1
[+] app.s3_bucket = sample-sam-bucket-12345
GENERATE SAM CONFIG FILE
[+] Loading contents of [config/sam.template.yml]: START
[+] Loading contents of [config/sam.template.yml]: END
[+] Writing contents to [tmp/sam.development.yml]: START
[+] Writing contents to [tmp/sam.development.yml]: DONE
[+] Generated [tmp/sam.development.yml]
[+] Executing [lambda-install-packages]
---------------------------------------------------
[+] 'scripts/install.sh' exists. Executing specified installation command(s)
---------------------------------------------------
docutils==0.14
jmespath==0.9.3
python-dateutil==2.7.3
six==1.11.0
---------------------------------------------------
[+] Completed [lambda-install-packages]
[+] Executing [lambda-optimize-package-size]
---------------------------------------------------
[+] find "$VIRTUAL_ENV/lib/python3.6/site-packages/" -name "*.so" | xargs strip
[-] Nothing to strip
[+] "$VIRTUAL_ENV/lib/python3.6/site-packages" -name "test" | xargs rm -rf
[+] "$VIRTUAL_ENV/lib/python3.6/site-packages" -name "tests" | xargs rm -rf
---------------------------------------------------
[+] 'scripts/optimize.sh' exists. Executing additional optimization command(s)
---------------------------------------------------
[+] Completed [lambda-optimize-package-size]
COMPLETED EXECUTION [build]
lambda.cli packageNOTE: Run aws configure first
(venv) [07:53:25] /var/task # lambda.cli package
CURRENT_WORKING_DIRECTORY = /var/task
RUNNING INSIDE VENV = True
COMMAND = package
CONTEXT, PARAMETERS, and DEFAULT VALUES
{
"app_name": "hello_world",
"command": "package",
"environment": "development",
"test_event": "event",
"version": false
}
EXECUTE COMMAND [package]
CONFIGURATION CHECKS [config/env.development.yml]
[+] resources.function.label = TargetFunction
[+] resources.function.name = TargetFunctionDevelopmentZZ
[+] app.stack_name = lambda-env-stack-dev-01
[+] app.region = us-east-1
[+] app.s3_bucket = sample-sam-bucket-12345
GENERATE SAM CONFIG FILE
[+] Loading contents of [config/sam.template.yml]: START
[+] Loading contents of [config/sam.template.yml]: END
[+] Writing contents to [tmp/sam.development.yml]: START
[+] Writing contents to [tmp/sam.development.yml]: DONE
[+] Generated [tmp/sam.development.yml]
[+] Executing [sam package --template-file tmp/sam.development.yml --output-template-file tmp/sam.development.packaged.yml --s3-bucket sample-sam-bucket-12345]
Uploading to d8b9d3c745f8e37af4512ffe3e4c91d4 236 / 236.0 (100.00%)
Successfully packaged artifacts and wrote output template to file tmp/sam.development.packaged.yml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /var/task/tmp/sam.development.packaged.yml --stack-name <YOUR STACK NAME>
[+] Completed [sam package --template-file tmp/sam.development.yml --output-template-file tmp/sam.development.packaged.yml --s3-bucket sample-sam-bucket-12345]
[+] Executing [lambda-generate-zip]
---------------------------------------------------
[-] '/tmp/app.zip' does not exist
---------------------------------------------------
[+] pushd /var/task/venv/lib/python3.6/site-packages/
/var/task/venv/lib/python3.6/site-packages /var/task
[+] zip -r -9 -q /tmp/app.zip * --exclude venv
[+] popd
/var/task
[+] cd /var/task/app
[+] zip -9 $FILE * --exclude "venv/" *.zip
adding: index.py (deflated 13%)
---------------------------------------------------
[+] '/tmp/app.zip' generated
---------------------------------------------------
[+] Completed [lambda-generate-zip]
[+] Executing [lambda-check-package-size]
---------------------------------------------------
[+] Uncompressed (./venv/lib/python3.6/site-packages): ~ 12M
---------------------------------------------------
[+] Compressed (/tmp/app.zip): ~ 3.6M
---------------------------------------------------
[+] Completed [lambda-check-package-size]
[+] Executing [aws s3 cp /tmp/app.zip s3://sample-sam-bucket-12345/d8b9d3c745f8e37af4512ffe3e4c91d4]
Completed 256.0 KiB/3.5 MiB (143.1 KiB/s) with 1 file(s) remaining
Completed 512.0 KiB/3.5 MiB (72.7 KiB/s) with 1 file(s) remaining
Completed 768.0 KiB/3.5 MiB (82.1 KiB/s) with 1 file(s) remaining
Completed 1.0 MiB/3.5 MiB (89.1 KiB/s) with 1 file(s) remaining
Completed 1.2 MiB/3.5 MiB (94.3 KiB/s) with 1 file(s) remaining
Completed 1.5 MiB/3.5 MiB (98.9 KiB/s) with 1 file(s) remaining
Completed 1.8 MiB/3.5 MiB (101.4 KiB/s) with 1 file(s) remaining
Completed 2.0 MiB/3.5 MiB (103.1 KiB/s) with 1 file(s) remaining
Completed 2.2 MiB/3.5 MiB (105.2 KiB/s) with 1 file(s) remaining
Completed 2.5 MiB/3.5 MiB (106.2 KiB/s) with 1 file(s) remaining
Completed 2.8 MiB/3.5 MiB (107.2 KiB/s) with 1 file(s) remaining
Completed 3.0 MiB/3.5 MiB (107.6 KiB/s) with 1 file(s) remaining
Completed 3.2 MiB/3.5 MiB (108.4 KiB/s) with 1 file(s) remaining
Completed 3.5 MiB/3.5 MiB (109.0 KiB/s) with 1 file(s) remaining
Completed 3.5 MiB/3.5 MiB (99.1 KiB/s) with 1 file(s) remaining
upload: ../../tmp/app.zip to s3://sample-sam-bucket-12345/d8b9d3c745f8e37af4512ffe3e4c91d4
[+] Completed [aws s3 cp /tmp/app.zip s3://sample-sam-bucket-12345/d8b9d3c745f8e37af4512ffe3e4c91d4]
COMPLETED EXECUTION [package]
Lambda CLI 1.0 Usage
$ lambda.cli init
[Development]
$ aws configure
$ lambda.cli configure
$ lambda.cli build
$ lambda.cli test --event event01
$ lambda.cli package
$ lambda.cli deploy
$ lambda.cli check-deployment
$ lambda.cli invoke --event event01
[Staging]
$ lambda.cli configure --staging
$ lambda.cli build --staging
$ lambda.cli invoke --staging
$ lambda.cli package --staging
$ lambda.cli deploy --staging
$ lambda.cli deploy --staging
[Production]
$ lambda.cli configure --production
$ lambda.cli build --production
$ lambda.cli package --production
$ lambda.cli deploy --production
$ lambda.cli invoke --production --event event01
/root/lambda_cli
├── __init__.py
├── build.py
├── check_deployment.py
├── command_line.py
├── config_utils.py
├── configure.py
├── deploy.py
├── display_help.py
├── initialize.py
├── invoke.py
├── lambda.cli
├── package.py
├── templates
│ ├── env.development.yml
│ ├── env.production.yml
│ ├── env.staging.yml
│ ├── event.json
│ ├── index.py
│ └── sam.template.yml
└── test.py
NOTE: /usr/bin/lambda.cli is symlinked to /root/lambda_cli/lambda.cli
sam.template.ymlAWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
Description
Globals:
Function:
Timeout: 30
MemorySize: 1024
Resources:
{{ resources.function.label }}:
Type: AWS::Serverless::Function
Properties:
CodeUri: ../app/
Handler: index.handler
Runtime: python3.6
FunctionName: {{ resources.function.name }}
env.development.ymlresources:
function:
label: 'TargetFunction'
name: ''
app:
stack_name: ''
region: ''
s3_bucket: ''
index.pydef handler(event, context):
print('> This is a sample function generated by LAMBDA CLI')
print(f"> EVENT = {event}")
return {}
app/index.py
import pandas as pd
import os
from fbprophet import Prophet
def absolute_path(filename):
dir_path = os.path.dirname(os.path.realpath(__file__))
output = os.path.join(dir_path, filename)
return output
def handler(event, context):
s = pd.Series([1, 3, 5, 7, 6, 8])
print(s)
df = pd.read_csv(absolute_path('data.csv'))
print(df.head())
m = Prophet()
m.fit(df)
future = m.make_future_dataframe(periods=365)
print(future.tail())
forecast = m.predict(future.tail(26))
print(forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail())
if __name__ == "__main__":
handler(event={}, context={})
app/data.csvSample data.csv can be obtained here
"ds","y"
"2007-12-10",9.59076113897809
"2007-12-11",8.51959031601596
"2007-12-12",8.18367658262066
"2007-12-13",8.07246736935477
"2007-12-14",7.8935720735049
. . .
config/sam.template.ymlAWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
TargetFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ../app
Handler: index.handler
Runtime: python3.6
MemorySize: 2048
Timeout: 30
scripts/install.sh#!/usr/bin/env bash
pip install pandas==0.23.4 --no-cache --verbose
pip install pyparsing==2.3.0 --no-cache --verbose
pip install pystan==2.17 --no-cache --verbose
pip install fbprophet==0.3.post2 --no-cache --verbose
scripts/optimize.sh#!/usr/bin/env bash
pip uninstall -y matplotlib
rm -rf "$VIRTUAL_ENV/lib/python3.6/site-packages/pystan/stan/src"
rm -rf "$VIRTUAL_ENV/lib/python3.6/site-packages/pystan/stan/lib/stan_math/lib"
The lambda-environment script initializes the virtual environment and activates the virtual environment prepared. The virtual environment directory is created in the directory mounted in the container.
The lambda-install-packages script looks for the /var/tasks/scripts/install.sh provided by the user and executes that script if it exists. After the installation has completed, pip freeze is executed to allow the user to check the installed Python packages. This script requires the lambda-environment script executed first
This is an example of /var/tasks/scripts/install.sh script that involves relatively trickier installations (i.e. Facebook Prophet) which may not work right away using a requirements.txt installation approach.
#!/usr/bin/env bash
pip install pandas==0.23.4 --no-cache --verbose
pip install pyparsing==2.3.0 --no-cache --verbose
pip install pystan==2.17 --no-cache --verbose
pip install fbprophet==0.3.post2 --no-cache --verbose
The lambda-optimize-package-size script performs two things: (1) default optimization and (2) custom optimization specified by the user. This script looks for /var/task/scripts/optimize.sh and executes it if it exists. This script is usually performed before the application is packaged and proves to be useful when dealing with packages (i.e. machine learning and data engineering packages) and files that increase the file size of the zip file for the AWS Lambda Function.
This is an example /var/task/scripts/optimize.sh script that reduces the size of the uncompressed package from 300-500MB+ to ~ 130MB for AWS Lambda functions that use pystan and fbprophet.
#!/usr/bin/env bash
pip uninstall -y matplotlib
rm -rf "$VIRTUAL_ENV/lib/python3.6/site-packages/pystan/stan/src"
rm -rf "$VIRTUAL_ENV/lib/python3.6/site-packages/pystan/stan/lib/stan_math/lib"
The lambda-generate-zip script generates a zip file from the packages installed in the virtual environment (/var/task/venv/lib/python3.6/site-packages/) and the files in the working directory (/var/task). The generated zip file can be found in the /tmp directory: /tmp/app.zip. This is usually executed after the lambda-optimize-package-size script to reduce the size of the package significantly
lambda-check-package-sizeThe lambda-check-package-size simply calculates the total size of the uncompressed and compressed package to allow the user to perform additional optimization work in case the total size exceeds the AWS Lambda limit.
The lambda-get-s3-package-location script simply extracts the Amazon S3 Zip file location from the SAM package YAML file. The uploaded ZIP file will be replaced by the generated zip file after using the lambda-generate-zip script. The AWS S3 CLI upload will be used to replace the zip file in the target Amazon S3 bucket
vimtmuxtreeIPython (embed)Content type
Image
Digest
Size
737.5 MB
Last updated
over 7 years ago
docker pull arvslat/lambda-env