You are on page 1of 36

12-Factor Apps

Docker Community All Hands-On - Mar 11, 2021


Quick Intro

● Full-time at Virginia Tech since 2011


○ Helping lead cloud strategy and platform efforts
○ Adjunct Faculty with VT CS Dept since S16

● Community Involvement
○ Docker Captain and Community Leader

● Husband and dad to four girls and a boy!

@mikesir87
A long, long time ago...

● When shipping was done mostly between land/sea…


○ Goods were packaged mostly in barrels, crates, and sacks
○ Ships often spent more time in dock than on the sea because of the time it
took to load/unload
○ Chance of loss/theft was very high

@mikesir87 Credit: http://www.worldshipping.org/about-the-industry/history-of-containerization/before-container-shipping


Enter the Industrial Revolution

● When rail became more common…


○ Goods frequently went from ship to train, train to train, and train to ship
○ Inadequacies of load/unload processes compounded

● A better shipping practice was needed

@mikesir87 Credit: https://en.wikipedia.org/wiki/Rail_freight_in_Great_Britain


The Shipping Container

● With the shipping container…


○ Goods producers can simply load a box with whatever they want to ship
○ The shipping industry can focus around a standardized box
○ Goods can be moved much more efficiently
○ Chance of loss/theft plummeted

@mikesir87
Yes! We ship software!

● Deploying software has always been painful, but was manageable


● Now, we want to adapt to users and to changing requirements
● The faster pace of shipping software highlights the pain points
○ Especially true in microservice-based applications
○ Applies to even running small functions on data

@mikesir87
What’s it mean to be “cloud native”?
“Cloud-native is about
how applications are
created and deployed,
not where.”
-- Pivotal

@mikesir87
Source: https://pivotal.io/cloud-native
Background on Heroku

● Founded in 2009 as a “platform-as-a-service”


○ Currently supports easy deployment of apps written in Node, Ruby, Go, PHP,
Java, Scala, Clojure, and Python

● “Heroku is a cloud platform that lets companies build, deliver,


monitor and scale apps — we're the fastest way to go from idea to
URL, bypassing all those infrastructure headaches.”

@mikesir87
12-Factor Background

● Drafted by developers at Heroku


○ Based on experience of helping build and deploy
hundreds of apps on the Heroku platform

● Initially presented in 2011


○ Presented by Adam Wiggins, cofounder of Heroku

● Available at 12factor.net

@mikesir87
The 12 Factors

1. Codebase 7. Port Binding

2. Dependencies 8. Concurrency

3. Config 9. Disposability

4. Backing Services 10. Dev/prod parity

5. Build, release, run 11. Logs

6. Processes 12. Admin Processes


1. Codebase

● Track using Git, SVN, Mercurial, something


● A one-to-one relationship between app and repo
○ Multiple repos isn’t a single app, but a distributed system.
Each component is an app that can be 12-factor compliant
○ Multiple apps from same codebase should be refactored as
libraries and included via dependency management

● Multiple deploys of the same app

@mikesir87
2. Dependencies

● Dependencies are explicitly declared in a manifest


○ Applies both for development and production
○ Java with pom.xml, Node with package.json, Ruby with Gemfile, etc.

● Applications should NOT rely on system-wide packages


○ Includes usage of system tools (even curl, bash, etc.)
○ Not all systems may have them available

@mikesir87
3. Config

● There should be strict separation of code and config


○ Config should NOT be bundled with the app

● Config = everything that can vary between deploys


○ Resource credentials (database, caches, API keys, etc.)
○ Hostname information

● Config should be provided with a combination of environment


variables, injected files, configuration services

@mikesir87
4. Backing Services

● Treat backing services (db, caches, etc.) as attached resources


● The code for a twelve-factor app makes no distinction between
local and third party services
● Goal is to be able to swap out a local resource with a managed
resource with no change to the code itself
○ Example - Swapping local MySQL with RDS instance

@mikesir87
5. Build, release, run

● Strictly separate build and run stages


○ Build stage transforms code into an executable bundle
○ Run runs the bundle in a execution environment, combined with the config

● Each release should have a unique ID and be traceable back to the


originating source

@mikesir87
6. Processes

● Apps should be executed as one or more stateless processes


● Each process should share nothing. Any data that needs to be
persisted/shared should be done using a backing service
● Sticky sessions are a violation of twelve-factor

@mikesir87
7. Port Binding

● All apps/services should exposed via port binding


○ Should be fully self-contained and expose itself, rather than rely on a pre-
existing HTTP server

@mikesir87
8. Concurrency

● Scale out by adding more processes


● Often talked about scaling horizontally rather than vertically
○ Horizontally refers to adding more machines, which start more processes
○ Vertically refers to adding more resources to existing processes to let them do
more

@mikesir87
9. Disposability

● Apps should start and stop quickly and gracefully


○ Respond to SIGTERM events and start shutdown

● Includes idea of being “robust against sudden death”


○ Ensure message queues timeout and requeue messages if a handler dies due
to hardware failure

@mikesir87
10. Dev/Prod Parity

● Keep all environments as consistent as possible


○ Supports easier ability for continuous deployment

● Use same backing services between dev and prod


○ Don’t use one queue/db in one tier and another type in another tier

@mikesir87
11. Logs

● Treat logs as event streams


○ Try to have one event per line (exception traces may span multiple lines)
○ No beginning or end as long as the app is running

● Stay away from routing or storage of the output stream within the
app
○ Simply send data to stdout/stderr and let environment configuration
determine where it should go

@mikesir87
12. Admin Processes

● Run admin/management tasks as one-off processes


○ Database migrations or other one-time scripts

● One-off scripts should utilize same dependency isolation


techniques of apps themselves

@mikesir87
How do containers help here?
Containers (when done right) solve...

1. Codebase 7. Port Binding

2. Dependencies 8. Concurrency

3. Config 9. Disposability

4. Backing Services 10. Dev/prod parity

5. Build, release, run 11. Logs

6. Processes 12. Admin Processes


Let’s talk config...

● There are generally two methods to store config in the


environment:
○ Environment variables - variables defined in the environment and passed
into running processes. These can be overridden/changed at runtime.
○ Files - configuration is stored in a file and the app reads the config from the
file.

@mikesir87
When to use env vars?

● Environment variables are completely fine for…


○ Changing options
○ Changing labels
○ Configuring non-secret data

● They should very rarely be used for secret data


○ Environment variables are leaked far too often (logs, debug info, etc.)
○ Often these leaky locations are used when sending debug info to third-parties
(yikes!)

@mikesir87
Config in Files

● By storing config in files, we remove the possibility of it being


leaked in environment variable dumps
● To be compromised, someone would need filesystem access
● App can then read the contents of the file and configure
○ Does require an understanding of the file structure/schema (JSON vs YAML vs
plain-text, etc.)

@mikesir87
Where do the files come from?
apiVersion: v1

Injecting Secrets
kind: Pod
metadata:
name: mypod
spec:

● Leverage the orchestration framework!


containers:
- name: mypod
image: mysql
● Secrets are created/stored by the volumeMounts:
- name: foo
framework mountPath: "/secrets"
readOnly: true

● The files are mounted directly into the env:


- name: MYSQL_PASSWORD_FILE

container’s filesystem value: /secrets/mysql-pword


volumes:
- name: foo
secret:
secretName: mysecret
items:
- key: password
path: mysql-pword
@mikesir87
Using Init Containers

● Decouple how the secrets are fetched and provided to the app
● Uses two containers with a shared volume
○ The init container loads/fetches the secrets and puts them in the volume
○ The main app container reads the secrets from the volume

Init Container App Container

Shared Volume

Pod/ECS Task
@mikesir87
apiVersion: v1

Init Container - Kubernetes kind: Pod


metadata:
name: app
spec:
● The app container only starts after initContainers:
- name: secrets-fetcher

the secrets-fetcher container exits image: startup-image


volumeMounts:

successfully - name: sharedData


mountPath: /secrets
containers:
- name: app
image: my-app

secrets-fetcher app volumeMounts:


- name: sharedData

sharedData mountPath: /secrets


volumes:
- name: sharedData

Pod/ECS Task emptyDir:


medium: Memory

@mikesir87
What’s it mean to be “cloud native”?
“Cloud-native is about
how applications are
created and deployed,
not where.”
-- Pivotal

@mikesir87
Source: https://pivotal.io/cloud-native
What’s it mean to be “cloud native”?
It means to remove impediments and support the software
industrial revolution!
Thanks! Questions?

You might also like