Herokuのpythonチュートリアル(Getting Started with Python on Heroku)をやってみた
ここ。 Getting Started on Heroku with Python | Heroku Dev Center
Heroku は Ruby や Rails を使ったことがなかったので今まで敬遠してたんだけど
python のチュートリアルがあったのでやってみた。
つまずいたポイントとか覚えておきたいことなど、軽くメモ。
heroku アカウントでログインしながら進めるとチャプターに線が引かれていくのいいすね。
(2015/11/01 追記)
こちらのチュートリアルは Heroku での開発手順や基本的なコマンドを学ぶにはいいですが、
Heroku での Django アプリケーション開発という点ではこちらの方が詳しい。
Getting Started on Heroku with Python | Heroku Dev Center
(追記ここまで)
(2015/12/22 追記)
Heroku で Django アプリケーション開発をするためのテンプレートがありました。
(追記ここまで)
Introduction
- Heroku アカウントを作成する
- python をインストールする
- Setuptools, pip, virtualenv をインストールする
- Postgres をインストールする
virtualenv のあたりは昔 このあたりの記事 を読んで pyenv と pyenv-virtualenv というプラグインを入れていたので、そのまま。
Postgres については、公式ドキュメント だと Postgres.app でインストールするようにとの記載があるけど、これまた昔に Homebrew 経由で入れてたっぽい。
ただ、知らず知らずのうちに brew upgrade
とかしてたせいか
$ postgres -D /usr/local/var/postgres LOG: skipping missing configuration file "/usr/local/var/postgres/postgresql.auto.conf" FATAL: database files are incompatible with server DETAIL: The data directory was initialized by PostgreSQL version 9.3, which is not compatible with this version 9.4.4.
というエラーが表示されてしまったので、以下の記事を参考に修正。
homebrewで PostgreSQL 9.3へバージョンアップ方法 - Qiita
Setup
特になし。 Heroku toolbelt をインストールした後、
$ heroku login
でログイン。
Prepare the app
$ git clone https://github.com/heroku/python-getting-started.git
Deploy the app
$ heroku create [アプリ名]
で heroku 上にアプリを作成する。[アプリ名]
は省略可能で、省略すると heroku が勝手に lit-bastion-5032
みたいなアプリ名をつける。
$ git push heroku master
でデプロイ 。
$ heroku open
でブラウザでアプリを確認。
View logs
$ heroku logs --tail
でログが見られる。
Define a Procfile
Procfile
という名前のテキストファイルが重要で、アプリの起動時に実行するコマンドを記述する。
詳しくはここ。
The Procfile | Heroku Dev Center
Procfile は
web: gunicorn gettingstarted.wsgi --log-file -
のように、<process type>: <command>
というフォーマットで記述する。
<process type>
は web
, worker
など。
Scale the app
Free の dyno の特徴としては
- 30 分アクセスがなければスリープする
- 1 日の稼働時間の max は 18 h
- 18 h を越えなければスリープ状態の dyno にアクセスがあってもリクエストはさばいてくれる。ただし、起動のため通常のアクセスよりも時間がかかる(2 回目以降のアクセスでは普通)
Declare app dependencies
python アプリの場合、依存関係のあるライブラリは requirements.txt
というファイルをルートディレクトリ直下に置くことで管理する。
チュートリアルの requirements.txt
はこんな感じ。
dj-database-url==0.3.0 Django==1.8.1 django-postgrespool==0.3.0 gunicorn==19.3.0 psycopg2==2.6 SQLAlchemy==1.0.4 whitenoise==1.0.6
ローカルで pip freeze
を実行するとインストールされているライブラリの一覧が確認できる。
自分の場合はこんな感じ。
$ pip freeze Django==1.5.10 GoogleAppEngineCloudStorageClient==1.9.5.0 Pillow==2.5.3 PyYAML==3.05 astroid==1.3.2 beautifulsoup4==4.3.2 django-appengine-toolkit==0.2.1 django-blog-zinnia==0.13 django-mptt==0.6.1 django-tagging==0.3.2 django-xmlrpc==0.1.5 logilab-common==0.62.1 mercurial==3.3.2 pep8==1.5.7 pylint==1.4.0 pyparsing==2.0.2 pytz==2014.7 six==1.9.0 virtualenv==1.11.6 wsgiref==0.1.2
heroku にデプロイする時、自動的に以下のコマンドが実行され、依存関係のあるライブラリがインストールされるらしい。
$ pip install -r requirements.txt --allow-all-external
ローカルの pyenv 環境を構築する
公式ドキュメントだと
$ virtualenv venv
というコマンドで venv
という virtualenv 環境を新しく作ってるみたいだけど、私の場合は pyenv-virtualenv を使っているので
$ pyenv install 2.7.8 $ pyenv virtualenv 2.7.8 venv $ pyenv local venv # .python-version ファイルを .gitignore に追記しておく $ echo .python-version >> .gitignore # 新しく作成した venv という環境下で依存ライブラリをインストール $ pip install -r requirements.txt --allow-all-external $ pyenv rehash
とした。runtime.txt
見ると python-2.7.9
と書いてるんだけど記事執筆時点で pip で 2.7.9 がインストールできないようなので
とりあえず 2.7.8 にした。
Run the app locally
$ python manage.py collectstatic
としたところ、以下のようなエラーがでた。
File "/Users/yama/.pyenv/versions/2.7.8/lib/python2.7/hmac.py", line 8, in <module> from operator import _compare_digest as compare_digest ImportError: cannot import name _compare_digest
結局根本的な原因はわからなかったんだけど、pyenv で 2.7.8 を再インストールしたり色々試してたら直った。
元々 python の 2.7.5 を入れていて、2.7.5 の時はエラーの原因になっている hmac.py に from operator import ...
という行がないのが関係している?
$ pyenv global 2.7.5 $ python Python 2.7.5 (default, Aug 16 2014, 22:15:56) [GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import operator >>> operator._copare_digest Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'module' object has no attribute '_copare_digest' >>> $ pyenv global 2.7.8 $ pyenv rehash $ python Python 2.7.8 (default, Jul 25 2015, 00:35:51) [GCC 4.2.1 Compatible Apple LLVM 6.1.0 (clang-602.0.53)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import operator >>> operator._compare_digest <built-in function _compare_digest>
(というか、別件で 2.7.5 使った後、単に pyenv rehash
し忘れただけ?)
参考:
Python/Flask error: "ImportError: cannot import name _compare_digest" - Stack Overflow
python manage.py collectstatic
というコマンドについて
Django の機能で、プロジェクト内に散らばっている静的リソース(チュートリアルでは local assets と呼んでる)を 1 つのディレクトリにまとめることができる。
ためしにチュートリアルの gettingstarted/settings.py
を見てみると、
INSTALLED_APPS = ( ... 'django.contrib.staticfiles', 'hello' ) ... STATIC_ROOT = 'staticfiles' STATIC_URL = '/static/' STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'static'), )
のように書かれている。ポイントは 、
STATIC_ROOT
は 1 箇所にまとめた静的リソースをどこに置くかを指定する (この場合ルートディレクトリ直下にstaticfiles
というディレクトリが作成され、そこにまとめられる)- 基本的には
STATIC_DIRS
に指定したディレクトリから静的リソースが収集される - ↑に加え、各アプリケーションディレクトリの
static
というディレクトリに配置した静的リソースは自動的に収集される
(しないように設定することもできる)
参考:
django.contrib.staticfilesを使う - 偏った言語信者の垂れ流し
静的ファイルの公開方法 — Django 1.4 documentation
静的リソースを staticfiles
にまとめた後、heroku local web
というコマンドでローカルでアプリを起動してみる。
が、ここでもエラーが。
$ heroku local web 08:57:05 web.1 | started with pid 4825 08:57:05 web.1 | [2015-07-25 08:57:05 +0900] [4825] [INFO] Starting gunicorn 19.3.0 08:57:05 web.1 | [2015-07-25 08:57:05 +0900] [4825] [INFO] Listening at: http://0.0.0.0:5000 (4825) 08:57:05 web.1 | [2015-07-25 08:57:05 +0900] [4825] [INFO] Using worker: sync 08:57:05 web.1 | [2015-07-25 08:57:05 +0900] [4857] [INFO] Booting worker with pid: 4857 08:57:07 web.1 | [2015-07-24 23:57:07 +0000] [4857] [ERROR] Error handling request 08:57:07 web.1 | Traceback (most recent call last): 08:57:07 web.1 | File "/Users/yama/.pyenv/versions/heroku/lib/python2.7/site-packages/gunicorn/workers/sync.py", line 130, in handle 08:57:07 web.1 | self.handle_request(listener, req, client, addr) 08:57:07 web.1 | File "/Users/yama/.pyenv/versions/heroku/lib/python2.7/site-packages/gunicorn/workers/sync.py", line 171, in handle_request 08:57:07 web.1 | respiter = self.wsgi(environ, resp.start_response) 08:57:07 web.1 | File "/Users/yama/.pyenv/versions/heroku/lib/python2.7/site-packages/whitenoise/base.py", line 62, in __call__ 08:57:07 web.1 | return self.application(environ, start_response) 08:57:07 web.1 | File "/usr/local/google_appengine/lib/django-1.5/django/core/handlers/wsgi.py", line 236, in __call__ 08:57:07 web.1 | self.load_middleware() 08:57:07 web.1 | File "/usr/local/google_appengine/lib/django-1.5/django/core/handlers/base.py", line 55, in load_middleware 08:57:07 web.1 | raise exceptions.ImproperlyConfigured('Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname)) 08:57:07 web.1 | ImproperlyConfigured: Middleware module "django.contrib.auth.middleware" does not define a "SessionAuthenticationMiddleware" class
なぜか google app engine の django を参照しているっぽい。。。
で、確認してみたところどうやら昔 GAE python の勉強をしていた時に
ここ を参考に .zshrc
にパスを追加していたのが原因のよう。
PYTHONPATH="/usr/local/google_appengine:/usr/local/google_appengine/lib/django-1.5:$PYTHONPATH" export PYTHONPATH
これを削除してターミナルを再起動したらちゃんと動いた。
Push local changes
特になし。ローカルでの変更をコミットして git push heroku master
すると heroku にデプロイできますよという話。
Provision add-ons
デフォルトでは、heroku は 1500 行分のログしか保存してくれない。
そのため papertrail というアドオンをインストールする。
「heroku addon logging」とかでググって出て来るのは papertrail の他、Logentries とかも同じような機能のアドオンなのかな?(未確認)
また、アドオンのインストールは事前にアカウントを verify する必要がある。
verify なしだとこんなメッセージが表示される。
$ heroku addons:create papertrail ! Please verify your account to install this add-on plan (please enter a credit card) For more information, see https://devcenter.heroku.com/categories/billing Verify now at https://heroku.com/verify
メッセージに記載されている通り、https://heroku.com/verify にアクセスしてクレジットカード情報や住所を入力する。
Start a console
heroku run
というコマンドで Heroku 上でコマンドを実行できる。
そのときに使われるのが one-off dyno という一時的な dyno らしい。
Define config vars
heroku で環境変数を利用するための config vars について。
ローカル環境の場合、.env
というファイルに記述すると heroku local
時に自動的に export して使うことができる。
.env
に
TIMES=2
と書くと、python 側で
import os times = int(os.environ.get('TIMES', 3))
といった書き方で取得できる。
また、デプロイされている heroku アプリケーションに対しては heroku config
コマンドを使う。
# config vars をセットする $ heroku config:set TIMES=2 # config vars を確認する $ heroku config === glacial-tor-4711 Config Vars DATABASE_URL: postgres://*************** PAPERTRAIL_API_TOKEN: ********** TIMES: 2
Provision a database
heroku では様々なデータベースをアドオンとして用意してある。
たぶん一番一般的なのが Postgres で、チュートリアルのアプリには最初から heroku-postgres アドオンが追加されている。
$ heroku addons
=== Resources for glacial-tor-4711
Plan Name Price
--------------------------- -------------------- -----
heroku-postgresql:hobby-dev cooking-surely-2089 free
papertrail:choklad dreaming-purely-9244 free
heroku pg
コマンドを使うと DB についてのもう少し詳しい情報が見られる。
$ heroku pg === DATABASE_URL Plan: Hobby-dev Status: Available Connections: 0/20 PG Version: 9.4.4 Created: 2015-07-24 13:11 UTC Data Size: 7.0 MB Tables: 11 Rows: 40/10000 (In compliance) Fork/Follow: Unsupported Rollback: Unsupported Add-on: cooking-surely-2089
参考: Heroku Postgres | Heroku Dev Center
また、チュートリアルのコードでは /db
にアクセスするとデータベースへの保存が行われる。
views.py (抜粋)
def db(request): greeting = Greeting() greeting.save() greetings = Greeting.objects.all() return render(request, 'db.html', {'greetings': greetings})
ただし、このままアクセスするとエラーになる。
ProgrammingError at /db
autocommit cannot be used inside a transaction
テーブルがまだ作成されていないかららしい。
これを解決するためには manage.py migrate
コマンドを Heroku 上で実行する。
$ heroku run python manage.py migrate Running `python manage.py migrate` attached to terminal... up, run.1059 Synchronizing apps without migrations: Creating tables... Creating table hello_greeting Running deferred SQL... Installing custom SQL... Running migrations: Rendering model states... DONE Applying contenttypes.0001_initial... OK ...
このコマンドを実行した後に再度 /db
にアクセスすると、今度は正常に表示される。
TODO:
ローカルだとまだ動かないみたい。heroku local web
で起動した後、http://localhost:5000/db にアクセスすると
OperationalError at /db
could not connect to server: No such file or directory
Is the server running locally and accepting
connections on Unix domain socket "/tmp/.s.PGSQL.5432"?
というエラーが出る。
Postgres を Homebrew からインストールしたのが原因...?
もうちょい調べる。
Next steps
この次に読むべきものとして 2 つの URL が紹介されている。
感想
とりあえず基本的な使い方はわかった!
前はデプロイしてとりあえず動かす時点で断念したけど、このコードを元にすればデプロイまでは大丈夫そう。
データベースの設定周りがよく理解できてないのと、Procfile
で使っている gunicorn というライブラリがわからないのでそれは今後の課題として。