$30 off During Our Annual Pro Sale. View Details »

Heroku 101 – PyCon 2014

Heroku 101 – PyCon 2014

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

Jacob Kaplan-Moss

April 09, 2014
Tweet

More Decks by Jacob Kaplan-Moss

Other Decks in Programming

Transcript

  1. lanyrd.com/scxqhw
    Heroku 101
    !
    [email protected]
    @jacobian

    View Slide

  2. lanyrd.com/scxqhw
    about:me

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  32. lanyrd.com/scxqhw
    Create and deploy 

    the application

    View Slide

  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

    View Slide

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

    View Slide

  35. lanyrd.com/scxqhw
    Logging

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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/')
    def setname(name):
    db.set('name', name)
    return 'Name updated.'
    hello.py

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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…

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  65. lanyrd.com/scxqhw
    log2viz

    View Slide

  66. lanyrd.com/scxqhw
    Librato Metrics

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  71. lanyrd.com/scxqhw
    Questions and Free Play
    [email protected]
    @jacobian

    View Slide