Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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 Assumptions You want to learn about Heroku. You know

    (a bit) of Python. You have Python, pip, and virtualenv*.
  2. 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
  3. lanyrd.com/scxqhw import os from flask import Flask ! app =

    Flask(__name__) ! @app.route('/') def hello(): return 'Hello World!' hello.py
  4. lanyrd.com/scxqhw $ git init $ git add . $ git

    commit -m "initial commit" Store the app in git
  5. 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
  6. 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
  7. lanyrd.com/scxqhw $ git add . $ git commit -m "add

    logging" $ git push heroku master Commit and deploy again
  8. 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
  9. lanyrd.com/scxqhw $ git add . $ git commit -m "add

    logging" $ git push heroku master Commit and deploy
  10. 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
  11. 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
  12. 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
  13. 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
  14. lanyrd.com/scxqhw $ git add . $ git commit -m "add

    logging" $ git push heroku master Commit and deploy
  15. 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…
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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