Whether you treat Lambda as the connective tissue for your EC2-centric AWS environment, or are deploying fully-serverless infrastructure, you’ll want to have a good methodology in place for developing, testing, building, and deploying your Lambda functions. Lambda itself is not at all prescriptive in this regard; it ultimately expects you to deliver a zipped code payload, but everything up until that point is up to you.
Crucially, since Lambda functions are a software product, it is important to apply our usual software engineering principles and practices to their development. We want to be able to develop locally, have good unit test coverage within a modular design, manage internal & external dependencies, create builds, and deploy to multiple environments. In other words, Lambda may have a novel execution model, but it should nevertheless conform to existing engineering processes. Fortunately, this is quite easy to achieve with a set of project structure conventions, and a trusty utility: Unix
We’ve put together a sample repo detailing a set of conventions that achieves these objectives.
Rather than a single monolithic file of Lambda code, as the code textbox on the Lambda console suggests, it makes sense to structure the Lambda function as an entry-point with a package:
. ├── Makefile # Definition of `make` targets. ├── builds # Builds directory. │ ├── deploy-2016-08-15_16-50.zip │ └── deploy-2016-08-15_16-54.zip ├── index.py # Entry point for the Lambda function. ├── lambda_package # Python package `lambda_package`. │ ├── __init__.py │ ├── localcontext.py │ ├── utility.py ├── requirements # External dependencies. │ ├── common.txt │ ├── dev.txt │ └── lambda.txt └── tests # Unit tests for the package. ├── __init__.py └── lambda_package ├── __init__.py ├── test_localcontext.py └── test_utility.py
make targets in place, we can easily develop the Lambda code against a local Python runtime; this vastly shortens the development feedback loop.
# Install development dependencies (e.g. unit testing framework). make init # Invoke the function locally. make invoke # Run the unit test suite. make test
We can even simulate the AWS Lambda
context object by passing in a
def handler(event, context): """Entry point for the Lambda function.""" # Lambda starts here. if __name__ == '__main__': from lambda_package.localcontext import LocalContext handler(None, LocalContext())
Finally, if our Lambda function has complex interactions with other AWS resources, we can run it under an IAM profile with identical permissions to the runtime Lambda role. In short, local development of Lambda functions need not be any different than any other class of software that your organization produces.
When it’s time to build and deploy, we can turn again to
# Gathers runtime dependencies and our custom code into a single zip archive: # e.g. builds/deploy-2016-08-15_16-50.zip make build # Deploys to the specified Lambda ARN: export ARN=arn:aws:lambda:us-west-2:111111111111:function:my-function-name make deploy
With these CI/CD primitives, the possibilities are endless: multiple environment deploys, blue-green deployment, orchestration via a build system such as TravisCI (as shown in the sample repo), etc. With Lambda, you can craft the precise DevOps workflow that suits your organizational and environmental needs, without ever needing to relax the foundational engineering principles you use to build all other software.