Slide 1

Slide 1 text

lanyrd.com/scxqhw Heroku 101 ! jacob@heroku.com @jacobian

Slide 2

Slide 2 text

lanyrd.com/scxqhw about:me

Slide 3

Slide 3 text

lanyrd.com/scxqhw Co-BDFL about:me

Slide 4

Slide 4 text

lanyrd.com/scxqhw Director of Security Co-BDFL about:me

Slide 5

Slide 5 text

lanyrd.com/scxqhw What is Heroku? The application platform

Slide 6

Slide 6 text

lanyrd.com/scxqhw You write your app. We do the rest.

Slide 7

Slide 7 text

lanyrd.com/scxqhw LOW HIGH SCALE drag to scale

Slide 8

Slide 8 text

lanyrd.com/scxqhw 5 Billion requests per day 3+ Million Apps Created 125+ Add-on services

Slide 9

Slide 9 text

lanyrd.com/scxqhw What’s a Heroku app?

Slide 10

Slide 10 text

lanyrd.com/scxqhw What’s a Heroku app?

Slide 11

Slide 11 text

lanyrd.com/scxqhw What’s a Heroku app?

Slide 12

Slide 12 text

lanyrd.com/scxqhw The Twelve-Factor app 12factor.net

Slide 13

Slide 13 text

lanyrd.com/scxqhw Let’s dive in!

Slide 14

Slide 14 text

lanyrd.com/scxqhw Assumptions You want to learn about Heroku. You know (a bit) of Python. You have Python, pip, and virtualenv*.

Slide 15

Slide 15 text

lanyrd.com/scxqhw * install.python-guide.org

Slide 16

Slide 16 text

lanyrd.com/scxqhw Install the Toolbelt toolbelt.heroku.com

Slide 17

Slide 17 text

lanyrd.com/scxqhw $ heroku login Log in

Slide 18

Slide 18 text

lanyrd.com/scxqhw X. Dev/prod parity 12factor.net/dev-prod-parity

Slide 19

Slide 19 text

lanyrd.com/scxqhw Create a Flask app devcenter.heroku.com/articles/getting-started-with-python

Slide 20

Slide 20 text

lanyrd.com/scxqhw $ mkdir hello-heroku $ cd hello-heroku $ virtualenv venv $ source venv/bin/activate $ pip install Flask gunicorn Create a vitualenv and install requirements

Slide 21

Slide 21 text

lanyrd.com/scxqhw import os from flask import Flask ! app = Flask(__name__) ! @app.route('/') def hello(): return 'Hello World!' hello.py

Slide 22

Slide 22 text

lanyrd.com/scxqhw web: gunicorn hello.py Procfile

Slide 23

Slide 23 text

lanyrd.com/scxqhw VI. Processes 12factor.net/

Slide 24

Slide 24 text

lanyrd.com/scxqhw $ foreman start Start the app

Slide 25

Slide 25 text

lanyrd.com/scxqhw X. Dev/prod parity 12factor.net/dev-prod-parity

Slide 26

Slide 26 text

lanyrd.com/scxqhw $ pip freeze > requirements.txt Free dependencies

Slide 27

Slide 27 text

lanyrd.com/scxqhw II. Dependencies 12factor.net/

Slide 28

Slide 28 text

lanyrd.com/scxqhw Store the app in git devcenter.heroku.com/articles/git

Slide 29

Slide 29 text

lanyrd.com/scxqhw venv *.pyc .gitignore

Slide 30

Slide 30 text

lanyrd.com/scxqhw $ git init $ git add . $ git commit -m "initial commit" Store the app in git

Slide 31

Slide 31 text

lanyrd.com/scxqhw I. Single codebase 12factor.net/

Slide 32

Slide 32 text

lanyrd.com/scxqhw Create and deploy 
 the application

Slide 33

Slide 33 text

lanyrd.com/scxqhw $ heroku create $ git push heroku master ... -----> Launching... done, v2 http://happy-pycon-2014.herokuapp.com deployed to Heroku $ heroku open Create and deploy the app

Slide 34

Slide 34 text

lanyrd.com/scxqhw V. Build, release, run 12factor.net/build-release-run

Slide 35

Slide 35 text

lanyrd.com/scxqhw Logging

Slide 36

Slide 36 text

lanyrd.com/scxqhw $ heroku logs --tail Viewing logs

Slide 37

Slide 37 text

lanyrd.com/scxqhw import os from flask import Flask ! app = Flask(__name__) ! import logging logging.basicConfig(level=logging.DEBUG) ! @app.route('/') def hello(): logging.info("saying hello") return 'Hello World!' hello.py

Slide 38

Slide 38 text

lanyrd.com/scxqhw $ git add . $ git commit -m "add logging" $ git push heroku master Commit and deploy again

Slide 39

Slide 39 text

lanyrd.com/scxqhw $ heroku logs --tail Viewing logs

Slide 40

Slide 40 text

lanyrd.com/scxqhw XI. Logs as event streams 12factor.net/

Slide 41

Slide 41 text

lanyrd.com/scxqhw Configuration variables devcenter.heroku.com/articles/config-vars

Slide 42

Slide 42 text

lanyrd.com/scxqhw import os import logging from flask import Flask ! app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) ! @app.route('/') def hello(): logging.info("saying hello") name = os.environ.get('NAME', 'World') return 'Hello %s!' % name hello.py

Slide 43

Slide 43 text

lanyrd.com/scxqhw $ NAME=Jacob foreman start Try it out locally

Slide 44

Slide 44 text

lanyrd.com/scxqhw $ git add . $ git commit -m "add logging" $ git push heroku master Commit and deploy

Slide 45

Slide 45 text

lanyrd.com/scxqhw $ heroku config:set NAME=Jacob Setting config vars and restarting happy-pycon-2014... done, v6 ! $ heroku config:get NAME jacob ! $ heroku config === happy-pycon-2014 Config Vars NAME: jacob ! $ heroku config:unset NAME Unsetting NAME and restarting happy-pycon-2014... done, v7 Managing config vars

Slide 46

Slide 46 text

lanyrd.com/scxqhw III. Store config in the environment 12factor.net/

Slide 47

Slide 47 text

lanyrd.com/scxqhw Releases devcenter.heroku.com/articles/releases

Slide 48

Slide 48 text

lanyrd.com/scxqhw $ heroku releases === happy-pycon-2014 Releases v7 Remove NAME config vars jacob@heroku.com 2014/04/09 12:41:49 (~ 1m ago) v6 Deploy df3cf06 jacob@heroku.com 2014/04/09 12:40:32 (~ 2m ago) v5 Set NAME config vars jacob@heroku.com 2014/04/09 12:35:30 (~ 7m ago) v4 Deploy 5bebf9b jacob@heroku.com 2014/04/09 12:34:27 (~ 8m ago) v3 Deploy 69398b3 jacob@heroku.com 2014/04/09 12:18:08 (~ 24m ago) v2 Enable Logplex jacob@heroku.com 2014/04/09 12:16:20 (~ 26m ago) v1 Initial release jacob@heroku.com 2014/04/09 12:16:17 (~ 26m ago) Release history

Slide 49

Slide 49 text

lanyrd.com/scxqhw I. One codebase, many deploys 12factor.net/

Slide 50

Slide 50 text

lanyrd.com/scxqhw Addons addons.heroku.com

Slide 51

Slide 51 text

lanyrd.com/scxqhw $ heroku addons:add rediscloud Adding rediscloud on happy-pycon-2014... done, v8 (free) Use `heroku addons:docs rediscloud` to view documentation. Adding an addon

Slide 52

Slide 52 text

lanyrd.com/scxqhw import os import redis from flask import Flask ! app = Flask(__name__) db = redis.from_url(os.environ['REDISCLOUD_URL']) ! @app.route('/') def hello(): name = db.get('name') if name is None: name = 'World' return 'Hello %s!' % name ! @app.route('/setname/') def setname(name): db.set('name', name) return 'Name updated.' hello.py

Slide 53

Slide 53 text

lanyrd.com/scxqhw IV. Treat backing services as attached resources 12factor.net/

Slide 54

Slide 54 text

lanyrd.com/scxqhw $ pip install redis $ pip freeze > requirements.txt Dependencies

Slide 55

Slide 55 text

lanyrd.com/scxqhw $ git add . $ git commit -m "add logging" $ git push heroku master Commit and deploy

Slide 56

Slide 56 text

lanyrd.com/scxqhw X. Dev/prod parity? Uh oh…

Slide 57

Slide 57 text

lanyrd.com/scxqhw $ foreman start 13:03:34 web.1 | started with pid 24492 ... 13:03:34 web.1 | 2014-04-09 13:03:34 [24495] [ERROR] Exception in worker process: 13:03:34 web.1 | Traceback (most recent call last): ... 13:03:34 web.1 | KeyError: 'REDISCLOUD_URL' ... 13:03:34 web.1 | 2014-04-09 13:03:34 [24495] [INFO] Worker exiting (pid: 24495) 13:03:34 web.1 | 2014-04-09 13:03:34 [24492] [INFO] Shutting down: Master 13:03:34 web.1 | 2014-04-09 13:03:34 [24492] [INFO] Reason: Worker failed to boot. 13:03:34 web.1 | exited with code 3 13:03:34 system | sending SIGTERM to all processes SIGTERM received Uh oh…

Slide 58

Slide 58 text

lanyrd.com/scxqhw Some solutions: Only run remotely Homebrew - brew.sh Vagrant - vagrantup.com Docker - docker.io

Slide 59

Slide 59 text

lanyrd.com/scxqhw Scaling and performance devcenter.heroku.com/articles/scaling

Slide 60

Slide 60 text

lanyrd.com/scxqhw $ heroku ps:scale web=2 Scaling dynos... done, now running web at 2:1X. ! $ heroku ps:scale web=10 Scaling dynos... done, now running web at 10:1X. ! $ heroku ps:scale web=5:2X Scaling dynos... done, now running web at 5:2X. ! $ heroku ps:scale web=1:PX Scaling dynos... done, now running web at 1:PX. ! $ heroku ps:scale web=1:1X Scaling dynos... done, now running web at 1:1X. Scaling

Slide 61

Slide 61 text

lanyrd.com/scxqhw Understanding performance devcenter.heroku.com/articles/optimizing-dyno-usage

Slide 62

Slide 62 text

lanyrd.com/scxqhw VIII. Concurrency via processes 12factor.net/

Slide 63

Slide 63 text

lanyrd.com/scxqhw $ heroku labs:enable log-runtime-metrics $ heroku restart ! $ heroku logs --tail 2014-04-09T17:19:30.746857+00:00 heroku[web.1]: source=web.1 dyno=heroku. 23939571.b4d17f84-50f5-4e2f-9fb1-b2124db4addb sample#memory_total=17.86MB sample#memory_rss=17.85MB sample#memory_cache=0.00MB sample#memory_swap=0.00MB sample#memory_pgpgin=5311pages sample#memory_pgpgout=740pages ! 2014-04-09T17:19:50.787234+00:00 heroku[web.1]: source=web.1 dyno=heroku. 23939571.b4d17f84-50f5-4e2f-9fb1-b2124db4addb sample#memory_total=17.86MB sample#memory_rss=17.85MB sample#memory_cache=0.00MB sample#memory_swap=0.00MB sample#memory_pgpgin=5311pages sample#memory_pgpgout=740pages log-runtime-metrics

Slide 64

Slide 64 text

lanyrd.com/scxqhw Understanding usage data log2viz - log2viz.herokuapp.com Librato - metrics.librato.com

Slide 65

Slide 65 text

lanyrd.com/scxqhw log2viz

Slide 66

Slide 66 text

lanyrd.com/scxqhw Librato Metrics

Slide 67

Slide 67 text

lanyrd.com/scxqhw Bonus: Platform API devcenter.heroku.com/categories/platform-api

Slide 68

Slide 68 text

lanyrd.com/scxqhw >>> import json >>> import requests # python-requests.org ! >>> heroku = requests.session() >>> heroku.auth = ('', '{INSERT HEROKU API TOKEN HERE}') >>> heroku.headers['Accept'] = 'application/vnd.heroku+json; version=3' >>> heroku.headers['Content-Type'] = 'application/json' REST API basics

Slide 69

Slide 69 text

lanyrd.com/scxqhw >>> heroku.get('https://api.heroku.com/apps/happy-pycon-2014').json() {u'archived_at': None, ... u'web_url': u'http://jacobian.herokuapp.com/'} ! >>> heroku.get('https://api.heroku.com/apps/happy-pycon-2014/config-vars').json() [35] : {u'NAME': u'Jacob', u'REDISCLOUD_URL': u'{REDACTED}'} ! >>> body = json.dumps({'NAME': 'Robot'}) >>> heroku.patch('https://api.heroku.com/apps/happy-pycon-2014/config-vars', body) App info; config vars

Slide 70

Slide 70 text

lanyrd.com/scxqhw >>> heroku.get('https://api.heroku.com/apps/happy-pycon-2014/formation').json() [{u'command': u'gunicorn hello:app', u'created_at': u'2014-09-08T16:42:24Z', u'id': u'928f6618-a0e2-4ded-95ec-e3a23a7795f0', u'quantity': 2, u'size': u'1X', u'type': u'web', u'updated_at': u'2014-09-08T18:50:02Z'}] ! >>> body = json.dumps({'quantity': 4}) >>> heroku.patch('https://api.heroku.com/apps/jacobian/formation/web', body) Scaling

Slide 71

Slide 71 text

lanyrd.com/scxqhw Questions and Free Play jacob@heroku.com @jacobian