Hello!
My name is Rich Jones!
Founder/CTO of gun.io!
Excellent jobs for Professional Freelancers
(ask me how afterwards!)
Author of..
I'm here to talk about..
Server-less Django!
..using Zappa!
This talk is going to move fast!
Better to be overwhelmed than bored!
Feel free to interrupt!
Ask me questions!
"What does serverless mean?!"
¯\_(ツ)_/¯
aka: "no permanent infrastructure"
AWS Lambda + AWS API Gateway
There still are servers, but they are ephemeral
Traditional web server:
Zappa:
~30ms!
By the time the user sees the page, the server has disappeared.
Advantages!
Super scalable!
1 request = 1 server
10 request = 10 server
100 requests = 100 servers
1000 requests = 1000 servers
10000 requests = 10000 servers
Orders of magnitude less expensive!
$0.000000002/ms
Plus 3,200,000 free seconds!
VPS: 4 * $20 * 12 = $960/yr
Zappa: $0.75 * 12 - Free = $0/yr
Zero maintainance!
Zero load balancing!
Zero security patches!
Zero downtime!
(fire your ops team)
"Cool! What else can it do?"
Event-driven architecture!
Code executes in response to events
File uploads!
Incoming emails!
New users!
"Neat! But I demand even more features!"
You're in luck!
Rollback!
$ zappa rollback -n 2
Free SSL certificates!
$ zappa certify
Log tailing!
$ zappa tail
Auto keep-warm!
Use C-Extensions via lambda-packages!
Load remote environment variables from S3!
// my-config-bucket/super-secret-config.json
{
    "DB_CONNECTION_STRING": "super-secret:database"
}
// zappa_settings.json
"remote_env_bucket": "my-config-bucket",
"remote_env_file": "super-secret-config.json"
# your_zappa_app.py
import os
db_string = os.environ('DB_CONNECTION_STRING')
CI/CD integration!
$ zappa status dev --json
Remote command invocation!
$ zappa invoke 'my_app.my_func'
$ zappa invoke 'print hello.upper()' --raw
Django management commands!
$ zappa manage makemigrations
Secure deployments!
"api_key_required": true
"iam_authorization": true
"authorizer": {
    "function": "your_module.your_auth_function",
    "result_ttl": 300,
    "token_source": "Authorization",
    "validation_expression": "^Bearer \\w+$",
}
Don't need to modify your existing apps!
No vendor lock-in!
Battle tested!
Used in production by banks, governments, medical companies and more!
Works with any WSGI application!
Django!
(Wagtail! django-cms! Pinax!)
Flask!
Pyramid!
Bottle!
Hug!
"Wow! Okay! How do I get started!"
It's super easy!
$ pip install zappa
$ zappa init


███████╗ █████╗ ██████╗ ██████╗  █████╗
╚══███╔╝██╔══██╗██╔══██╗██╔══██╗██╔══██╗
  ███╔╝ ███████║██████╔╝██████╔╝███████║
 ███╔╝  ██╔══██║██╔═══╝ ██╔═══╝ ██╔══██║
███████╗██║  ██║██║     ██║     ██║  ██║
╚══════╝╚═╝  ╚═╝╚═╝     ╚═╝     ╚═╝  ╚═╝


Welcome to Zappa!

Zappa is a system for running server-less Python web applications on AWS Lambda and AWS API Gateway.
This `init` command will help you create and configure your new Zappa deployment.
Let's get started!

Your Zappa configuration can support multiple production environments, like 'dev', 'staging', and 'production'.
What do you want to call this environment (default 'dev'):
  {
      "dev": {
          "aws_region": "us-east-1",
          "django_settings": "demo.settings",
          "profile_name": "default",
          "s3_bucket": "my-demo-bucket"
      }
  }
  
$ zappa deploy
Calling deploy for stage dev..
Downloading and installing dependencies..
Packaging project as zip..
Uploading demo1-dev-1502298757.zip (11.1MiB)..
Scheduling..
Scheduled demo1-dev-zappa-keep-warm-handler.keep_warm_callback with expression rate(4 minutes)!
Uploading demo1-dev-template-1502298785.json (1.6KiB)..
Waiting for stack demo1-dev to create (this can take a bit)..
Deploying API Gateway..
Deployment complete!: https://8x24a8pmn7.execute-api.us-east-1.amazonaws.com/dev
BAM!
You're server-less!
..nearly. :(
Django is a bit less tolerant than Flask is here.
Django Gotcha #1:
ALLOWED_HOSTS = []
ALLOWED_HOSTS = [
  "8x24a8pmn7.execute-api.us-east-1.amazonaws.com",
  "your.cool.name"
]
 $ zappa update 
"Hooray!"
Django Gotcha #2:
Static files :(
Solution!:
 $ pip install django-storages boto
Thanks, Josh!
INSTALLED_APPS += ('storages',)
AWS_STORAGE_BUCKET_NAME = 'your-staticfiles-bucket'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
STATIC_URL = "https://%s/" % AWS_S3_CUSTOM_DOMAIN
STATICFILES_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
$ python manage.py collectstatic
$ zappa update
Yay!
Django Gotcha #3:
🐘
Database!
*uh-oh*
This is tricky!
Do you need a database?
Think serverlessly!
Avoid infrastructure!
ex: NoDB - incredibly simple, SQL-less, S3-based "database"
$ pip install nodb
from nodb import NoDB

nodb = NoDB()
nodb.bucket = "my-s3-bucket"
nodb.index = "name"

# Save an object!
user = {"name": "Jeff", "age": 19}
nodb.save(user)

# Load our object!
user = nodb.load("Jeff")
print user.age # 19

# Delete our object
nodb.delete("Jeff") # True
ex: zappa-bittorrent-tracker can serve millions of peers with S3-database
"But this is DjangoCon. We love the ORM! We want a real database!"
Okay fine :|
Two DB options: Use AWS RDS (expensive but easy) or EC2 (cheap but annoying)
"Let's do the easy one!"
Great choice!
"Now what the heck is a Virtual Private Cloud"
Great question!
Long answer!
"tl;dr plz"
A virtual "network" with a strict security policy
Prevent internet traffic from accessing your internal resources!
Allow traffic from your Lambdas to your database!
There are lots of pattern and policy options that you can make here!
Depends on your org's needs!
Consult your ops team!
"but you already said we could fire them.."
Simplest VPC for a basic Django app:
(configure with AWS web console)
One VPC,
two private subnets (for redudunancy),
allow internal TCP traffic on port 5432!
(+2 more public subnets +1 NAT Gateway to access 3rd party APIs)
  {
      "dev": {
          "aws_region": "us-east-1",
          "django_settings": "demo.settings",
          "profile_name": "default",
          "s3_bucket": "my-demo-bucket",
      }
  }
  
  {
      "dev": {
          "aws_region": "us-east-1",
          "django_settings": "demo.settings",
          "profile_name": "default",
          "s3_bucket": "my-demo-bucket",
          "vpc_config": {
              "SubnetIds": [
                "subnet-f3446aba",
                "subnet-c5b8c79e"
              ],
              "SecurityGroupIds": [
                "sg-9a9a1dfc"
              ]
          }
      }
  }
  
Okay, now we have a database and our Lambda can access it..
..but how can Django use it?
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'zappadbname',
        'USER': 'your-db-user',
        'PASSWORD': 'your-secure-db-password',
        'HOST': 'zappa-db.1234567.us-east-1.rds.amazonaws.com',
        'PORT': '5432'
    }

}
We can't access the database directly from our dev machines!
Create a new management command!
X
 $ pip install zappa-django-utils
INSTALLED_APPS += ('zappa_django_utils',)
$ zappa update
$ zappa manage create_pg_db
Database created from Lambda!
$ python manage.py makemigrations
$ zappa update
$ zappa manage migrate
$ zappa invoke --raw \
  "from django.contrib.auth.models import User; \
  User.objects.create_superuser( \
    'admin', \
    'admin@yourdomain.com', \
    'correct horse battery staple')"
X
$ zappa manage create_admin_user
Hooray!
🎉🐘🎉🐘🎉🐘🎉🐘
Django Gotcha #4: Domains
Zappa supports Let's Encrypt, but (now) prefers AWS ACM
  {
      "dev": {
          "aws_region": "us-east-1",
          "django_settings": "demo.settings",
          "profile_name": "default",
          "s3_bucket": "my-demo-bucket",
          "vpc_config": {
              "SubnetIds": [
                "subnet-f3446aba",
                "subnet-c5b8c79e"
              ],
              "SecurityGroupIds": [
                "sg-9a9a1dfc"
              ]
          },
          "domain": "your.cool.name",
          "certificate_arn": "arn:aws:acm:us-east-1:123:certificate/1234",
          
      }
  }
  
$ zappa certify
Hooray! We're live!
"So what about those events.."
Oh right!
Event-driven architecture!
This is the fun part!
Execute code in response to AWS ecosystem events!
No blocking pages!
No Celery!
Example 1: Avatar resizing
User uploads an image, we make a thumbnail
HTTP -> S3 -> Lambda -> S3
// zappa_settings.yml
events:
- function: users.util.process_avatar
  event_source:
    arn: arn:aws:s3:::your-upload-bucket
    events:
    - s3:ObjectCreated:*
 $ zappa schedule 
That's it! :D
*pro tip!*
Make sure you don't get stuck in an infinite loop!
// zappa_settings.yml
events:
- function: users.util.process_avatar
  event_source:
    arn: arn:aws:s3:::your-upload-bucket
    events:
    - s3:ObjectCreated:/uploads/*
Example 2: Daily notifications
Time is an event source!
Send daily notifications to your Slack channel!
// zappa_settings.yml
events:
- function: my_app.util.send_notifications
  expression: rate(24 hours)
 $ zappa schedule 
Hooray!
🎉 🎉 🎉 🎉
We can use cron and rate syntax!
Multiple schedules!
{
  "dev": {
     ...
     "events": [{
         "function": "my_app.utils.calc_status",
         "expressions": [
            "cron(0 20-23 ? * SUN-THU *)",
            "cron(0 0-8 ? * MON-FRI *)"
          ]
     }],
     ...
  }
}
"Sweet! But what if I don't want to wait for an event?
Zappa can execute functions asynchronously in a different Lambda!
Example 3: Let's bake a cake!
🍰
def bake_cake(*args, **kwargs):
     ingredients = get_ingredients()
     cake = bake(ingredients)
     deliver(cake)
from zappa.async import task

@task
def bake_cake(*args, **kwargs):
     ingredients = get_ingredients()
     cake = bake(ingredients)
     deliver(cake)
# url('api/order/cake')
def order_cake(request):
    bake_cake()
    return HttpResponse("Your cake is being made!")
It's that easy!
No config!
No queues!
No Celery!
Go nuts!
🍰🍰🍰🍰🍰🍰🍰🍰🍰🍰🍰🍰🍰🍰🍰🍰🍰🍰🍰🍰🍰
"But what if I need to bake mission-critical cakes for the whole planet?"
Globally-available server-less architecture!
"That sounds cool! But why do I want that?"
You might not need this..
..but it's cool to know you can do it.
Benefits of global deployments:
#1: Redunancy!
Cloud computing is an act of faith.
Amazon goes down too!
*gasp*
Amazon goes down too..
..but (usually) not across the planet.
Pro tip!
Don't host your status page on your own infrastructure!
fail
#2: Speed!
$ ping apigateway.us-east-2.amazonaws.com # Ohio
--- apigateway.us-east-2.amazonaws.com ping statistics ---
round-trip min/avg/max/stddev = 35.274/39.257/42.731/2.499 ms
$ ping apigateway.ap-northeast-1.amazonaws.com # Tokyo
--- apigateway.ap-northeast-1.amazonaws.com ping statistics ---
round-trip min/avg/max/stddev = 177.616/202.745/305.468/46.281 ms
>200ms just for the round trip!
(Earth is big!)
Please provide non-US users with equally great service!
#3: Scalability!
1 region: ~5,000 simultaneous connections
9 regions: ~45,000 simultaneous connections!
1,420,092,000,000 hits/year!
(Trillion! With a t!)
#4: Security!
Compartmentalize data, limit damage.
Defend against internal and external threats!
Prevent non-US employees accessing US data, etc.
PSA: This is a big topic. I'm not saying deploying globally makes you more secure by default. Consult your local hacker for more details.
#5: Regulatory compliance!
Different countries have different laws.
Medical Data
Financial Data
Personally Identifying Information
Private Communications
Data Retention
Etcetera..
(I'm not your lawyer. Thank goodness.)
One product may have different compliance needs in many countries.
"Well, I'm convinced! How do we do it?"
$ zappa init
Do you want to make this a global application? Y
{
    "dev_ap_northeast_1": {
        "app_function": "geocake.app",
        "aws_region": "ap-northeast-1",
        "domain": "dev-ap-northeast-1.geocake.biz",
        "s3_bucket": "lmbda_dev_ap_northeast_1"
    },
    "dev_ap_northeast_2": {
        "app_function": "geocake.app",
        "aws_region": "ap-northeast-2",
        "domain": "dev-ap-northeast-2.geocake.biz",
        "s3_bucket": "lmbda_dev_ap_northeast_2"
    },

    [ ... ],

    "dev_us_west_2": {
        "app_function": "geocake.app",
        "aws_region": "us-west-2",
        "domain": "dev-us-west-2.geocake.biz",
        "s3_bucket": "lmbda_dev_us_west_2"
    }
}
$ zappa deploy --all
$ zappa certify --all
Bam!
httpstat https://dev-ap-northeast-1.geocake.biz/
httpstat https://dev-ap-northeast-2.geocake.biz/
...
httpstat https://dev-us-west-2.geocake.biz/
  DNS Lookup   TCP Connection   SSL Handshake   Server Processing   Content Transfer
[     5ms    |       8ms      |     50ms     |       42ms       |        0ms       ]
             |                |               |                   |                  |
    namelookup:5ms            |               |                   |                  |
                        connect:13ms          |                   |                  |
                                    pretransfer:75ms              |                  |
                                                      starttransfer:198ms            |
                                                                                 total:198ms
RAWK \m/
..but now we have to make a decision.
*uh-oh*
How do we route our web traffic?
Great question!
Multiple strategies for "routing policy"
Latency!
"Use the fastest region"
Geolocation!
"Choose the region based on the country of origin"
(They're all pretty easy to set up, just check the right boxes in Route 53 console.)
Let me know what you decide!
Aside: should this be a feature in Zappa? Would you find that useful?
🐘
Databases!
The magic bullet:
"Read Replica"
One primary, many replicas.
Reads: Fast! Local!
Writes: Slow! Remote!
Django example:
DATABASES = {
    'default': {},
    'medical': {
        'NAME': 'medical_db',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_user',
        'PASSWORD': 'swordfish',
    },
    'primary': {
        'NAME': 'primary',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_user',
        'PASSWORD': 'spam',
    },
    'replica': {
        'NAME': 'replica',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_user',
        'PASSWORD': 'eggs',
    }
}
class MedicalRouter(object):
    """
    A router to control all database operations on models in the
    medical application.
    """
    def db_for_write(self, model, **hints):
        if model._meta.app_label == 'medical':
            return 'medical_db'
        return None

    def db_for_read(self, model, **hints):
        if model._meta.app_label == 'medical':
            return 'medical_db'
        return None

DATABASE_ROUTERS = [
  'path.to.MedicalRouter',
  'path.to.PrimaryReplicaRouter'
  ]
Fast local reads!
Slow global writes!
Tip: Use AJAX for writes!
In conclusion!
Save time and money,
build awesome event-driven applications,
never have downtime,
be fast everywhere,
use Zappa!
(your favorite companies are!)
Want to contribute? 😃
100+ contributors,
6 continents!
🐧
Ways you can help:
Use Zappa!
Join the Slack! https://slack.zappa.io
Report bugs!
Fix bugs!
Pull request triage!
Python 3.6 packaging and testing!
Windows maintainer!
Docs!
Tutorials!
Support for non-AWS vendors!
Work on a library of common functions!
  shoutz
  
Patrons:
https://patreon.com/Zappa
Special Thanks:
Do you need awesome, scalable, server-less apps?
Hire me! 💸💸💸
Email: rich@gun.io
Code: https://github.com/Miserlou/Zappa
Slack: https://slack.zappa.io
Thank you!
Questions?