The “Twelve-Factor” application model has come to represent twelve best practices for building modern, cloud-native applications. With guidance on things like configuration, deployment, runtime, and multiple service communication, the Twelve-Factor model prescribes best practices that apply to everything from web applications to APIs to data processing applications.
Although serverless computing and AWS Lambda have changed how application development is done, the “Twelve-Factor” best practices remain relevant and applicable in a serverless world. In this talk, Chris will share with you how to apply the “Twelve-Factor” model to serverless application development with AWS Lambda and Amazon API Gateway and show you how these services enable you to build scalable, low cost, and low administration applications.
2. About me:
Chris Munns - munns@amazon.com, @chrismunns
• Senior Developer Advocate - Serverless
• New Yorker
• Previously:
• AWS Business Development Manager – DevOps, July ’15 - Feb ‘17
• AWS Solutions Architect Nov, 2011- Dec 2014
• Formerly on operations teams @Etsy and @Meetup
• Little time at a hedge fund, Xerox and a few other startups
• Rochester Institute of Technology: Applied Networking and
Systems Administration ’05
• Internet infrastructure geek
4. The “12 Factor” model & serverless applications
• 12 Factor applications were popularized by developers building
large scale applications on platforms such as Heroku
• In recent years the 12 Factor guidelines have been considered best
practices for both developers and operations engineers regardless
of the application’s use-case and at nearly any scale
• Many of the 12 Factor guidelines align directly with best practices
for serverless applications and are improved upon given the nature
of AWS Lambda, Amazon API Gateway, and other AWS services
• However, some of the 12 Factor guidelines don’t directly align with
serverless applications or are interpreted very differently
14. Common Lambda use cases
Web
Applications
• Static
websites
• Complex web
apps
• Packages for
Flask and
Express
Data
Processing
• Real time
• MapReduce
• Batch
Chatbots
• Powering
chatbot logic
Backends
• Apps &
services
• Mobile
• IoT
</></>
Amazon
Alexa
• Powering
voice-enabled
apps
• Alexa Skills
Kit
IT
Automation
• Policy engines
• Extending
AWS services
• Infrastructure
management
15. The 12 Factors:
1. Codebase
2. Dependencies
3. Config
4. Backing
services
5. Build, release,
run
6. Process
7. Port Binding
8. Concurrency
9. Disposability
10.Dev/prod
parity
11.Logs
12.Admin
processes
Let’s explore how the 12 Factors apply to a serverless
application:
16. 1. Codebase
12Factor.net: “One codebase tracked in revision control, many
deploys”
Serverless Apps: All code should be stored in revision control (a
development best practice). The same repository should be used for all
environments deployed to. The bounds of an “application” differ in
serverless terms:
• If events are shared (ie. a common Amazon API Gateway) then
Lambda function code for those events should be put in the same
repository
• Otherwise break “services” along event source into their own
repositories
17. 12Factor.net: “Explicitly declare and isolate dependencies”
Serverless Apps: Code that needs to be used by multiple functions
should be packaged into its own library. Include those packages inside
of your deployment package. Every language Lambda supports has a
model for this:
2. Dependencies
Node.js & Python
• .zip file consisting of
your code and any
dependencies
• Can use npm/pip.
• All dependencies must
be at root level
Java
• Either .zip file with all
code/dependencies, or
standalone .jar with
compiled class &
resource files at root
level, required jars in /lib
directory
• Can use Maven
C# (.NET Core)
• Either .zip file with all
code/dependencies,
or a standalone .dll
• Can use Nuget
• All assemblies (.dll)
at root level
18. 3. Config
12Factor.net: “Store config in the environment”
Serverless Apps: Many ways to do this in serverless applications:
• Lambda Environment Variables:
• Key-value pairs available via standard environment variable APIs such as
process.env for Node.js or os.environ for Python
• Support KMS encryption
• API Gateway Stages:
• Key-value pairs available for configuring API Gateway functionality or to pass
on to HTTP endpoints as URI parameters or configuration parameters to a
Lambda invocation
• AWS Systems Manager Parameter Store:
19. AWS Systems Manager – Parameter Store
Centralized store to manage your
configuration data
• supports hierarchies
• plain-text or encrypted with KMS
• Can send notifications of changes
to Amazon SNS/ AWS Lambda
• Can be secured with IAM
• Calls recorded in CloudTrail
• Can be tagged
• Available via API/SDK
Useful for: centralized environment
variables, secrets control, feature
flags
from __future__ import print_function
import json
import boto3
ssm = boto3.client('ssm', 'us-east-1')
def get_parameters():
response = ssm.get_parameters(
Names=['LambdaSecureString'],WithDe
cryption=True
)
for parameter in
response['Parameters']:
return parameter['Value']
def lambda_handler(event, context):
value = get_parameters()
print("value1 = " + value)
return value # Echo back the first key
value
20. 4. Backing services
12Factor.net: “Treat backing services as attached resources”
Serverless Apps: No differences. Resources that Lambda functions
connect to, such as databases, should have their endpoints and
access credentials made available via config resources or IAM policies
👍
21. 5. Build, release, run
12Factor.net: “Strictly separate build and run stages”
Serverless Apps: No differences. Development best practices such
as Continuous Integration and Continuous Delivery should be followed.
• Use AWS CodeBuild and AWS CodePipeline to support this:
AWS CodeBuild AWS CodePipeline
23. version: 0.1
environment_variables:
plaintext:
"INPUT_FILE": "saml.yaml”
"S3_BUCKET": ""
phases:
install:
commands:
- npm install
pre_build:
commands:
- eslint *.js
build:
commands:
- npm test
post_build:
commands:
- aws cloudformation package --template $INPUT_FILE --s3-
bucket $S3_BUCKET --output-template post-saml.yaml
artifacts:
type: zip
files:
- post-saml.yaml
- beta.json
• Variables to be used by phases of
build
• Examples for what you can do in
the phases of a build:
• You can install packages or run
commands to prepare your
environment in ”install”.
• Run syntax checking,
commands in “pre_build”.
• Execute your build/test tools or
commands in “build”
• Execute the CloudFormation
“package” command to package
your serverless application with
SAM in “post_build”
• Create and store an artifact in S3
Serverless App buildspec.yml Example
24. Delivery via CodePipeline
Pipeline flow:
1. Commit your code to a source code repository
2. Package/Test in CodeBuild
3. Use CloudFormation actions in CodePipeline to
create or update stacks via SAM templates
Optional: Make use of ChangeSets
4. Make use of specific stage/environment
parameter files to pass in Lambda variables
5. Test our application between stages/environments
Optional: Make use of Manual Approvals
25. An example minimal Developer’s pipeline:
MyBranch-Source
Source
AWS CodeCommit
MyApplication
Build
test-build-source
AWS CodeBuild
MyDev-Deploy
create-changeset
AWS CloudFormation
execute-changeset
AWS CloudFormation
Run-stubs
AWS Lambda
This pipeline:
• Three Stages
• Builds code artifact
• One Development environment
• Uses SAM/CloudFormation to
deploy artifact and other AWS
resources
• Has Lambda custom actions for
running my own testing functions
26. Source
Source
AWS CodeCommit
MyApplication
An example minimal production pipeline:
Build
test-build-source
AWS CodeBuild
Deploy Testing
create-changeset
AWS
CloudFormation
execute-changeset
AWS
CloudFormation
Run-stubs
AWS Lambda
Deploy Staging
create-changeset
AWS
CloudFormation
execute-changeset
AWS
CloudFormation
Run-API-test
Runscope
QA-Sign-off
Manual Approval
Review
Deploy Prod
create-changeset
AWS
CloudFormation
execute-changeset
AWS
CloudFormation
Post-Deploy-Slack
AWS Lambda
This pipeline:
• Five Stages
• Builds code artifact
• Three deployed to “Environments”
• Uses SAM/CloudFormation to
deploy artifact and other AWS
resources
• Has Lambda custom actions for
running my own testing functions
• Integrates with a 3rd party
tool/service
• Has a manual approval before
deploying to production
27. 6. Process
12Factor.net: “Execute the app as one or more stateless processes”
Serverless Apps: This is inherent in how Lambda is designed
already:
• Lambda Functions should be treated as stateless despite the
potential to store some state in-between container re-use.
• There is no promise of container re-use between function
invocations.
• Data that needs to be kept should be stored off Lambda in a stateful
service such as a database or cache.
28. 7. Port Binding
12Factor.net: “Export services via port binding”
Serverless Apps: In Lambda/serverless applications this factor
doesn’t apply the same due to a difference in how Lambda Functions
are accessed:
• Instead of a “port” Lambda functions are invoked via one or more
triggering services or AWS’s APIs for Lambda
• When it comes to Lambda functions there are 3 models for how they
can be invoked; synchronously, asynchronously, and via stream
• Instead of having one function support multiple invocation sources,
create independent functions and make use of shared code via
dependencies (shared packages) to support shared capabilities
29. Lambda execution model
Synchronous (push) Asynchronous (event) Stream-based
Amazon
API Gateway
AWS Lambda
function
Amazon
DynamoDBAmazon
SNS
/order
AWS Lambda
function
Amazon
S3
reqs
Amazon
Kinesis
changes
AWS Lambda
service
function
30. Amazon S3 Amazon
DynamoDB
Amazon
Kinesis
AWS
CloudFormation
AWS CloudTrail Amazon
CloudWatch
Amazon
Cognito
Amazon SNSAmazon
SES
Cron events
DATA STORES ENDPOINTS
DEVELOPMENT AND MANAGEMENT TOOLS EVENT/MESSAGE SERVICES
Event sources that trigger AWS Lambda
and more!
AWS
CodeCommit
Amazon
API Gateway
Amazon
Alexa
AWS IoT AWS Step
Functions
31. 8. Concurrency
12Factor.net: “Scale out via the process model”
Serverless Apps: Doesn’t apply as Lambda functions will scale
automatically based on load. You can fork threads inside of your
function execution but there are practical limits due to the memory and
CPU/network constraints of your functions based on how you configure
them.
👍
32. 9. Disposability
12Factor.net: “Maximize robustness with fast startup and graceful
shutdown”
Serverless Apps: Shutdown doesn’t apply as Lambda functions and
their invocation are tied directly to incoming events. Speed at startup
does matter though and is a factor of deployment package size +
language used + VPC (or not) + pre-handler code calls.
👍
33. 10. Dev/prod parity
12Factor.net: “Keep development, staging, and production as similar
as possible”
Serverless Apps: This can be made incredibly easy with serverless
applications by:
• Making use of environment/stage variables or Parameter Store for
configuration information, backend resources, etc
• Using Serverless Application Models (SAM) to deploy your
application
• Can pass environment/stage variables via Parameters, Mappings, Imports
• Having a CI/CD process and tooling that supports multiple
environments or accounts
37. SAM template
AWSTemplateFormatVersion: '2010-09-09’
Transform: AWS::Serverless-2016-10-31
Resources:
GetHtmlFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/todo_list.zip
Handler: index.gethtml
Runtime: nodejs4.3
Policies: AmazonDynamoDBReadOnlyAccess
Events:
GetHtml:
Type: Api
Properties:
Path: /{proxy+}
Method: ANY
ListTable:
Type: AWS::Serverless::SimpleTable
Tells CloudFormation this is a SAM
template it needs to “transform”
Creates a Lambda function with the
referenced managed IAM policy,
runtime, code at the referenced zip
location, and handler as defined.
Also creates an API Gateway and
takes care of all
mapping/permissions necessary
Creates a DynamoDB table with 5
Read & Write units
39. SAM Local
CLI tool for local testing of serverless apps
Works with Lambda functions and “proxy-
style” APIs
Response object and function logs available
on your local machine
Uses open source docker-lambda images to
mimic Lambda’s execution environment:
• Emulates timeout, memory limits,
runtimes
https://github.com/awslabs/aws-sam-local
40. 11. Logs
12Factor.net: “Treat logs as event streams”
Serverless Apps: Logging (as well as Metric collection) are
considered a “universal right” in Lambda:
• Console output automatically collected and sent to Amazon
CloudWatch Logs
• Logs can be turned into Metrics
• Logs can be sent to Amazon S3 or Amazon ElasticSearch Service easily for
further inspection and trending
• Metrics for Lambda and API Gateway for several key stats are
automatically collected and sent to CloudWatch
• You can easily send more using the CloudWatch SDK
41. 12. Admin processes
12Factor.net: “Run admin/management tasks as one-off processes”
Serverless Apps: Doesn’t apply to Lambda since you already limit
your functions based on use case. True administrative tasks would
occur via their own Lambda Functions or via tools such as Amazon
EC2 Run Command.
👍
42. 1. Codebase
2. Dependencies
3. Config
4. Backing
services
5. Build, release,
run
6. Process
7. Port Binding
8. Concurrency
9. Disposability
10.Dev/prod
parity
11.Logs
12.Admin
processes
The 12 Factors & Serverless Applications:
As we’ve seen, 12 Factor application design can still be applied to
serverless applications taking into account some small differences!
= Works similarly = Not relevant
43. FIN, ACK (in closing)
As we’ve reviewed the 12 Factor methodology for
applications we’ve seen which factors do and do not apply
the same for serverless applications:
• Thinking about code reusability and how to scope your functions to
the smallest size necessary provides many benefits
• Factors related to underlying process management, network ports,
concurrency, and admin processes are largely not an issue in
serverless applications due to Lambda’s product design and
features
• Best practices for serverless align pretty closely with 12 Factor
guidance already, so you might be really close to meeting the “12
Factor bar” already!
https://12factor.net/codebase
There is always a one-to-one correlation between the codebase and the app:
*If there are multiple codebases, it’s not an app – it’s a distributed system. Each component in a distributed system is an app, and each can individually comply with twelve-factor.
*Multiple apps sharing the same code is a violation of twelve-factor. The solution here is to factor shared code into libraries which can be included through the dependency manager.
https://12factor.net/dependencies
A twelve-factor app never relies on implicit existence of system-wide packages. It declares all dependencies, completely and exactly, via a dependency declaration manifest. Furthermore, it uses a dependency isolation tool during execution to ensure that no implicit dependencies “leak in” from the surrounding system. The full and explicit dependency specification is applied uniformly to both production and development.
https://12factor.net/config
An app’s config is everything that is likely to vary between deploys (staging, production, developer environments, etc). This includes:
Resource handles to the database, Memcached, and other backing services
Credentials to external services such as Amazon S3 or Twitter
Per-deploy values such as the canonical hostname for the deploy
Apps sometimes store config as constants in the code. This is a violation of twelve-factor, which requires strict separation of config from code. Config varies substantially across deploys, code does not.
A litmus test for whether an app has all config correctly factored out of the code is whether the codebase could be made open source at any moment, without compromising any credentials.
https://12factor.net/backing-services
A backing service is any service the app consumes over the network as part of its normal operation. Examples include datastores (such as MySQL or CouchDB), messaging/queueing systems (such as RabbitMQ or Beanstalkd), SMTP services for outbound email (such as Postfix), and caching systems (such as Memcached).
Backing services like the database are traditionally managed by the same systems administrators as the app’s runtime deploy. In addition to these locally-managed services, the app may also have services provided and managed by third parties. Examples include SMTP services (such as Postmark), metrics-gathering services (such as New Relic or Loggly), binary asset services (such as Amazon S3), and even API-accessible consumer services (such as Twitter, Google Maps, or Last.fm).
The code for a twelve-factor app makes no distinction between local and third party services. To the app, both are attached resources, accessed via a URL or other locator/credentials stored in the config. A deploy of the twelve-factor app should be able to swap out a local MySQL database with one managed by a third party (such as Amazon RDS) without any changes to the app’s code. Likewise, a local SMTP server could be swapped with a third-party SMTP service (such as Postmark) without code changes. In both cases, only the resource handle in the config needs to change.
https://12factor.net/build-release-run
A codebase is transformed into a (non-development) deploy through three stages:
The build stage is a transform which converts a code repo into an executable bundle known as a build. Using a version of the code at a commit specified by the deployment process, the build stage fetches vendors dependencies and compiles binaries and assets.
The release stage takes the build produced by the build stage and combines it with the deploy’s current config. The resulting release contains both the build and the config and is ready for immediate execution in the execution environment.
The run stage (also known as “runtime”) runs the app in the execution environment, by launching some set of the app’s processes against a selected release.
The twelve-factor app uses strict separation between the build, release, and run stages. For example, it is impossible to make changes to the code at runtime, since there is no way to propagate those changes back to the build stage.
https://12factor.net/processes
The app is executed in the execution environment as one or more processes.
In the simplest case, the code is a stand-alone script, the execution environment is a developer’s local laptop with an installed language runtime, and the process is launched via the command line (for example, python my_script.py). On the other end of the spectrum, a production deploy of a sophisticated app may use many process types, instantiated into zero or more running processes.
Twelve-factor processes are stateless and share-nothing. Any data that needs to persist must be stored in a stateful backing service, typically a database.
The memory space or filesystem of the process can be used as a brief, single-transaction cache. For example, downloading a large file, operating on it, and storing the results of the operation in the database. The twelve-factor app never assumes that anything cached in memory or on disk will be available on a future request or job – with many processes of each type running, chances are high that a future request will be served by a different process. Even when running only one process, a restart (triggered by code deploy, config change, or the execution environment relocating the process to a different physical location) will usually wipe out all local (e.g., memory and filesystem) state.
https://12factor.net/port-binding
Web apps are sometimes executed inside a webserver container. For example, PHP apps might run as a module inside Apache HTTPD, or Java apps might run inside Tomcat.
The twelve-factor app is completely self-contained and does not rely on runtime injection of a webserver into the execution environment to create a web-facing service. The web app exports HTTP as a service by binding to a port, and listening to requests coming in on that port.
https://12factor.net/concurrency
Any computer program, once run, is represented by one or more processes. Web apps have taken a variety of process-execution forms. For example, PHP processes run as child processes of Apache, started on demand as needed by request volume. Java processes take the opposite approach, with the JVM providing one massive uberprocess that reserves a large block of system resources (CPU and memory) on startup, with concurrency managed internally via threads. In both cases, the running process(es) are only minimally visible to the developers of the app.
In the twelve-factor app, processes are a first class citizen. Processes in the twelve-factor app take strong cues from the unix process model for running service daemons. Using this model, the developer can architect their app to handle diverse workloads by assigning each type of work to a process type. For example, HTTP requests may be handled by a web process, and long-running background tasks handled by a worker process.
https://12factor.net/disposability
The twelve-factor app’s processes are disposable, meaning they can be started or stopped at a moment’s notice. This facilitates fast elastic scaling, rapid deployment of code or config changes, and robustness of production deploys.
Processes should strive to minimize startup time. Ideally, a process takes a few seconds from the time the launch command is executed until the process is up and ready to receive requests or jobs. Short startup time provides more agility for the release process and scaling up; and it aids robustness, because the process manager can more easily move processes to new physical machines when warranted.
Processes shut down gracefully when they receive a SIGTERM signal from the process manager. For a web process, graceful shutdown is achieved by ceasing to listen on the service port (thereby refusing any new requests), allowing any current requests to finish, and then exiting. Implicit in this model is that HTTP requests are short (no more than a few seconds), or in the case of long polling, the client should seamlessly attempt to reconnect when the connection is lost.
https://12factor.net/dev-prod-parity
Historically, there have been substantial gaps between development (a developer making live edits to a local deploy of the app) and production (a running deploy of the app accessed by end users). These gaps manifest in three areas:
The time gap: A developer may work on code that takes days, weeks, or even months to go into production.
The personnel gap: Developers write code, ops engineers deploy it.
The tools gap: Developers may be using a stack like Nginx, SQLite, and OS X, while the production deploy uses Apache, MySQL, and Linux.
The twelve-factor app is designed for continuous deployment by keeping the gap between development and production small. Looking at the three gaps described above:
Make the time gap small: a developer may write code and have it deployed hours or even just minutes later.
Make the personnel gap small: developers who wrote code are closely involved in deploying it and watching its behavior in production.
Make the tools gap small: keep development and production as similar as possible.
SAM Local is a CLI tool that allows customers to test their SAM-based Lambda functions locally, before deploying code to Lambda. SAM Local currently supports functions in Node.js, Java and Python. If your function is fronted by API Gateway, SAM Local will allow you to ping APIs to invoke your function. Alternatively, you can use SAM Local’s “event payload generator” to quickly create a mock event to invoke your function with. After your function executes, you can view the response or examine the logs, all on your local machine. SAM local automatically executes your functions in a sandboxed environment that mimics Lambda’s execution environment, by leveraging docker-lambda Docker images.
https://12factor.net/logs
Logs provide visibility into the behavior of a running app. In server-based environments they are commonly written to a file on disk (a “logfile”); but this is only an output format.
Logs are the stream of aggregated, time-ordered events collected from the output streams of all running processes and backing services. Logs in their raw form are typically a text format with one event per line (though backtraces from exceptions may span multiple lines). Logs have no fixed beginning or end, but flow continuously as long as the app is operating.
A twelve-factor app never concerns itself with routing or storage of its output stream. It should not attempt to write to or manage logfiles. Instead, each running process writes its event stream, unbuffered, to stdout. During local development, the developer will view this stream in the foreground of their terminal to observe the app’s behavior.
In staging or production deploys, each process’ stream will be captured by the execution environment, collated together with all other streams from the app, and routed to one or more final destinations for viewing and long-term archival. These archival destinations are not visible to or configurable by the app, and instead are completely managed by the execution environment. Open-source log routers (such as Logplex and Fluent) are available for this purpose.
https://12factor.net/admin-processes
The process formation is the array of processes that are used to do the app’s regular business (such as handling web requests) as it runs. Separately, developers will often wish to do one-off administrative or maintenance tasks for the app, such as:
Running database migrations (e.g. manage.py migrate in Django, rake db:migrate in Rails).
Running a console (also known as a REPL shell) to run arbitrary code or inspect the app’s models against the live database. Most languages provide a REPL by running the interpreter without any arguments (e.g. python or perl) or in some cases have a separate command (e.g. irb for Ruby, rails console for Rails).
Running one-time scripts committed into the app’s repo (e.g. php scripts/fix_bad_records.php).
One-off admin processes should be run in an identical environment as the regular long-running processes of the app. They run against a release, using the same codebase and config as any process run against that release. Admin code must ship with application code to avoid synchronization issues.
Note: Disposability scores somewhere in the middle. You don’t need to think about shutdown but you do need to think about startup performance in relation to your application