Heroku 101 – PyCon 2014

Heroku 101 – PyCon 2014

An introduction to Python on Heroku, given at PyCon 2014.

2f5463832ccb768ccb4a1ca3607c27ef?s=128

Jacob Kaplan-Moss

April 09, 2014
Tweet

Transcript

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

  2. lanyrd.com/scxqhw about:me

  3. lanyrd.com/scxqhw Co-BDFL about:me

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

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

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

  7. lanyrd.com/scxqhw LOW HIGH SCALE drag to scale

  8. lanyrd.com/scxqhw 5 Billion requests per day 3+ Million Apps Created

    125+ Add-on services
  9. lanyrd.com/scxqhw What’s a Heroku app?

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

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

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

  13. lanyrd.com/scxqhw Let’s dive in!

  14. lanyrd.com/scxqhw Assumptions You want to learn about Heroku. You know

    (a bit) of Python. You have Python, pip, and virtualenv*.
  15. lanyrd.com/scxqhw * install.python-guide.org

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

  17. lanyrd.com/scxqhw $ heroku login Log in

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

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

  20. 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
  21. lanyrd.com/scxqhw import os from flask import Flask ! app =

    Flask(__name__) ! @app.route('/') def hello(): return 'Hello World!' hello.py
  22. lanyrd.com/scxqhw web: gunicorn hello.py Procfile

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

  24. lanyrd.com/scxqhw $ foreman start Start the app

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

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

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

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

  29. lanyrd.com/scxqhw venv *.pyc .gitignore

  30. lanyrd.com/scxqhw $ git init $ git add . $ git

    commit -m "initial commit" Store the app in git
  31. lanyrd.com/scxqhw I. Single codebase 12factor.net/

  32. lanyrd.com/scxqhw Create and deploy 
 the application

  33. 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
  34. lanyrd.com/scxqhw V. Build, release, run 12factor.net/build-release-run

  35. lanyrd.com/scxqhw Logging

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

  37. 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
  38. lanyrd.com/scxqhw $ git add . $ git commit -m "add

    logging" $ git push heroku master Commit and deploy again
  39. lanyrd.com/scxqhw $ heroku logs --tail Viewing logs

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

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

  42. 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
  43. lanyrd.com/scxqhw $ NAME=Jacob foreman start Try it out locally

  44. lanyrd.com/scxqhw $ git add . $ git commit -m "add

    logging" $ git push heroku master Commit and deploy
  45. 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
  46. lanyrd.com/scxqhw III. Store config in the environment 12factor.net/

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

  48. 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
  49. lanyrd.com/scxqhw I. One codebase, many deploys 12factor.net/

  50. lanyrd.com/scxqhw Addons addons.heroku.com

  51. 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
  52. 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/<name>') def setname(name): db.set('name', name) return 'Name updated.' hello.py
  53. lanyrd.com/scxqhw IV. Treat backing services as attached resources 12factor.net/

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

    Dependencies
  55. lanyrd.com/scxqhw $ git add . $ git commit -m "add

    logging" $ git push heroku master Commit and deploy
  56. lanyrd.com/scxqhw X. Dev/prod parity? Uh oh…

  57. 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…
  58. lanyrd.com/scxqhw Some solutions: Only run remotely Homebrew - brew.sh Vagrant

    - vagrantup.com Docker - docker.io
  59. lanyrd.com/scxqhw Scaling and performance devcenter.heroku.com/articles/scaling

  60. 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
  61. lanyrd.com/scxqhw Understanding performance devcenter.heroku.com/articles/optimizing-dyno-usage

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

  63. 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
  64. lanyrd.com/scxqhw Understanding usage data log2viz - log2viz.herokuapp.com Librato - metrics.librato.com

  65. lanyrd.com/scxqhw log2viz

  66. lanyrd.com/scxqhw Librato Metrics

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

  68. 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
  69. 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
  70. 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
  71. lanyrd.com/scxqhw Questions and Free Play jacob@heroku.com @jacobian