DjangoをHeroku + uWSGIで動かしてみた

最近uWSGIにふれたため、HerokuでuWSGIを動かしてみようと思いました。

ただ、Herokuのチュートリアルではgunicornを動かしていました。
Getting Started on Heroku with Python | Heroku Dev Center

HerokuでuWSGIで動かす方法を調べたところ、uWSGIのドキュメントに記載がありました。
How to use Django with uWSGI | Django documentation | Django

そこで今回は、Django + uWSGIアプリをHerokuで動かしてみることにしました。

目次

   

環境

  • Mac OS X 10.11.6
  • Herokuアカウントは登録済、他は何もしていない
  • Python 3.5.2
  • Django 1.10.4
    • アプリの構成
      • PostgreSQL (9.6.1) を使用
        • Heroku上でmigrateやloaddataできるかも試す
      • 静的ファイルの配信あり
  • uWSGI 2.0.14
  • Djangoアプリ用のPythonライブラリ
    • dj-database-url0.4.1
    • psycopg2 2.6.2
    • whitenoise 3.2.2

 

MacでHeroku環境の準備

Homebrewによる Heroku Toolbeltのインストール

HomebrewでHeroku Toolbeltを管理したいため、Homebrewでインストールします。

# インストール
$ brew install heroku-toolbelt
...
🍺  /usr/local/Cellar/heroku/5.6.1-0976cf3: 12,942 files, 79.7M, built in 2 minutes 18 seconds

 

SSH鍵の登録

Herokuへpushする時に使用するSSH鍵を生成し、Herokuに登録します。
Managing Your SSH Keys | Heroku Dev Center

# 鍵生成
$ ssh-keygen -t rsa -b 4096 -C "you@example.com" -f ~/.ssh/id_rsa_heroku

# Herokuへログイン
$ heroku login
Enter your Heroku credentials.
Email: <typing>
Password (typing will be hidden): <typing>
Logged in as you@example.com

# HerokuにSSH鍵を追加
$ heroku keys:add ~/.ssh/id_rsa_heroku.pub
Uploading ~/.ssh/id_rsa_heroku.pub SSH key... done

 

MacPostgreSQLの準備

HomebrewでPostgreSQLをインストール

以下を参考に、HomebrewでPostgreSQLをインストールします。
MacにPostgreSQLをインストール - Qiita

# インストール
$ brew install postgresql
==> Installing dependencies for postgresql: readline
...
🍺  /usr/local/Cellar/readline/7.0.1: 46 files, 2M
==> Installing postgresql
...
==> /usr/local/Cellar/postgresql/9.6.1/bin/initdb /usr/local/var/postgres
==> Caveats
If builds of PostgreSQL 9 are failing and you have version 8.x installed,
you may need to remove the previous version first. See:
  https://github.com/Homebrew/homebrew/issues/2510

To migrate existing data from a previous major version (pre-9.0) of PostgreSQL, see:
  https://www.postgresql.org/docs/9.6/static/upgrading.html

To migrate existing data from a previous minor version (9.0-9.5) of PostgreSQL, see:
  https://www.postgresql.org/docs/9.6/static/pgupgrade.html

  You will need your previous PostgreSQL installation from brew to perform `pg_upgrade`.
  Do not run `brew cleanup postgresql` until you have performed the migration.

To have launchd start postgresql now and restart at login:
  brew services start postgresql
Or, if you don't want/need a background service you can just run:
  pg_ctl -D /usr/local/var/postgres start
==> Summary
🍺  /usr/local/Cellar/postgresql/9.6.1: 3,242 files, 36.4M

 

initdbの実行

以下を参考にinitdbを実行します。
initdb - PostgreSQL 9.6.1文書

initdb時の設定は以下の通りです。

# initdbの実行
$ initdb /usr/local/var/postgres -E utf8 --locale=C
The files belonging to this database system will be owned by user "<your_name>".
This user must also own the server process.

The database cluster will be initialized with locale "C".
The default text search configuration will be set to "english".

Data page checksums are disabled.

# エラー出た
initdb: directory "/usr/local/var/postgres" exists but is not empty
If you want to create a new database system, either remove or empty
the directory "/usr/local/var/postgres" or run initdb
with an argument other than "/usr/local/var/postgres".

 
エラーが出たためディレクトリを確認します。

$ cd /usr/local/var/
$ ls -al
total 0
drwxrwxr-x   6 you  admin  204 12 29 07:01 .
drwxr-xr-x  14 root          wheel  476 11 14 11:03 ..
drwxr-xr-x   4 you  admin  136 10 26 18:26 homebrew
drwxr-xr-x   2 you  admin   68 12  9 13:33 log
drwxr-xr-x  19 you  admin  646 12 17 07:21 mysql
drwx------  24 you  admin  816 12 29 07:01 postgres

 
エラーメッセージ通り、postgresディレクトリがすでに存在しているため、これを削除します。

# postgresディレクトリを削除
$ rm -r /usr/local/var/postgres
$ ls -al
total 0
drwxrwxr-x   5 you  admin  170 12 29 07:09 .
drwxr-xr-x  14 root          wheel  476 11 14 11:03 ..
drwxr-xr-x   4 you  admin  136 10 26 18:26 homebrew
drwxr-xr-x   2 you  admin   68 12  9 13:33 log
drwxr-xr-x  19 you  admin  646 12 17 07:21 mysql

 
もう一度initdbします。

# initdbの実行
$ initdb /usr/local/var/postgres -E utf8 --locale=C
...
WARNING: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the option -A, or
--auth-local and --auth-host, the next time you run initdb.

Success. You can now start the database server using:

    pg_ctl -D /usr/local/var/postgres -l logfile start

WARNINGは出ているものの、今回は開発環境のため無視します。

これでインストールが終わりました。

 

Mac起動時にPostgreSQL自動起動させる

インストール時の最後のメッセージに自動起動設定に関するメッセージが出ていました。

ただ、brew servicesでも管理できるとのことなので、以下を参考にHomebrewで自動起動設定を行います。
OS X に PostgreSQL を Homebrew でインストールして brew services で起動する - Qiita

# 自動起動設定
$ brew services start postgresql
==> Successfully started `postgresql` (label: homebrew.mxcl.postgresql)

$ brew services list
Name         Status  User         Plist
chromedriver stopped
mysql        stopped
postgresql   started <your_name> ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist

 

データベース確認

現在のデータベースを確認します。

# データベースの確認
$ psql -l
                                   List of databases
   Name    |    Owner     | Encoding | Collate | Ctype |       Access privileges
-----------+--------------+----------+---------+-------+-------------------------------
 postgres  | <your_name>  | UTF8     | C       | C     |
 template0 | <your_name>  | UTF8     | C       | C     | =c/<your_name>              +
           |              |          |         |       | <your_name>=CTc/<your_name>
 template1 | <your_name>  | UTF8     | C       | C     | =c/<your_name>              +
           |              |          |         |       | <your_name>=CTc/<your_name>
(3 rows)

 

ユーザの作成

Django向けに、postgresユーザを作成します。開発環境なので、パスワードは無しです。

まずは現在のユーザの一覧を確認します。

# 現在のユーザ一覧を確認
$ psql -q -c'select * from pg_user' postgres
   usename    | usesysid | usecreatedb | usesuper | userepl | usebypassrls |  passwd  | valuntil | useconfig
--------------+----------+-------------+----------+---------+--------------+----------+----------+-----------
 <your_name>  |       10 | t           | t        | t       | t            | ******** |          |

 
続いて、ユーザを作成します。

# ユーザを作成
$ createuser postgres

# 作成したユーザがいるか確認
$ psql -q -c'select * from pg_user' postgres
   usename    | usesysid | usecreatedb | usesuper | userepl | usebypassrls |  passwd  | valuntil | useconfig
--------------+----------+-------------+----------+---------+--------------+----------+----------+-----------
 <your_name>  |       10 | t           | t        | t       | t            | ******** |          |
 postgres     |    16386 | f           | f        | f       | f            | ******** |          |
(2 rows)

 

作成したユーザがオーナーのdatabaseを作成

postgresユーザがオーナーのdatabaseを作成します。

今回のデータベース名はtry_heroku_django_uwsgiとします。

# データベースを作成、Ownerは`postgres`ユーザ
$ createdb -O postgres try_heroku_django_uwsgi

# 作成したデータベースの確認
$ psql -l
                                          List of databases
          Name           |    Owner     | Encoding | Collate | Ctype |       Access privileges       
-------------------------+--------------+----------+---------+-------+-------------------------------
 postgres                | <your_name>  | UTF8     | C       | C     |
 template0               | <your_name>  | UTF8     | C       | C     | =c/<your_name>               +
                         |              |          |         |       | <your_name> =CTc/<your_name>
 template1               | <your_name>  | UTF8     | C       | C     | =c/<your_name>               +
                         |              |          |         |       | <your_name> =CTc/<your_name>
 try_heroku_django_uwsgi | postgres     | UTF8     | C       | C     |
(4 rows)

 
以上でMacPostgreSQLの準備が終わりました。

 

MacDjango + uWSGIアプリを作成

Django, uWSGIなどのPythonライブラリをインストール

virtualenvを使ってインストールします。

$ mkdir try_heroku_django_postgres
$ cd try_heroku_django_postgres/

# pyenvを有効化してvirtualenvを作成
$ eval "$(pyenv init -)"
$ python --version
Python 3.5.2
$ virtualenv env
...

# 必要なライブラリをインストール
$ source env/bin/activate
(env) $ pip install django uwsgi dj_database_url psycopg2 whitenoise
...
Successfully installed django-1.10.4 uwsgi-2.0.14 dj-database-url-0.4.1 psycopg2-2.6.2 whitenoise-3.2.2

 

Djangoアプリの作成
プロジェクトとアプリの作成
(env) $ django-admin startproject myproject .
(env) $ python manage.py startapp myapp

 

コードを書く

昔の自分のメモを参考に、今回の内容に合わせて作成します。 Django + Herokuでdj-ringo-tabetterを作った時の作業メモ - メモ的な思考的な

ソースコード全体をGitHubに上げましたので、詳しい内容は省略します。

作成した時のポイントは以下の通りです。

 

migrationとloaddata

Mac上でmigrationとloaddataができるかを試します。

(env) $ python manage.py makemigrations
...
(env) $ python manage.py migrate
...
(env) $ python manage.py loaddata initial_data
Installed 2 object(s) from 1 fixture(s)

 

requirements.txtを作成

HerokuでPythonライブラリをインストールするため、requirements.txtを作成します。

(env) $ pip freeze > requirements.txt

 

gitリポジトリの作成

Herokuへpushするため、gitリポジトリを作成します。

なお、今回はこのリポジトリ専用のuser.nameとuser.emailも設定します。

$ git init .

# リポジトリ専用のuser.nameとuser.emailを設定
$ git config user.name "<your_name>"
$ git config user.email "<your_email>@example.com"

# 確認
$ git config user.name
your_name
$ git config user.email
<your_email>@example.com

$ git add .
$ git commit -m 'add samples'

 

Heroku環境の構築

Herokuへログイン
# ログイン
(env)$ heroku login
Enter your Heroku credentials.
# メールアドレスを入力
Email: <you@example.com>
# パスワードを入力
Password (typing will be hidden):
Logged in as <you@example.com>

 

Herokuアプリを作成

今回は開発的なものなので、アプリ名はデフォルトのままにします。

# Herokuアプリを作成
(env)$ heroku create
Creating app... done, ⬢ <foo-bar-1234>
https://<foo-bar-1234>.herokuapp.com/ | https://git.heroku.com/<foo-bar-1234>.git

 

Heroku Postgresを追加
# Heroku Postgresを追加
(env)$ heroku addons:create heroku-postgresql:hobby-dev
Creating heroku-postgresql:hobby-dev on ⬢ <foo-bar-1234>... free
Database has been created and is available
 ! This database is empty. If upgrading, you can transfer
 ! data from another database with pg:copy
Created postgresql-<ham-5678> as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation

 

DjangoアプリのリポジトリをHerokuへpushしてデプロイ
# Herokuへpush
(env)$ git push heroku master
...
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Python app detected
# runtime.txtで指定したバージョンのPythonがインストールされる
remote: -----> Installing python-3.5.2
# requirements.txtで指定したPythonライブラリがインストールされる
remote:      $ pip install -r requirements.txt
...
remote:        Successfully installed Django-1.10.4 dj-database-url-0.4.1 psycopg2-2.6.2 uWSGI-2.0.14 whitenoise-3.2.2
# collectstaticが実行される
remote:      $ python manage.py collectstatic --noinput
remote:        62 static files copied to '/tmp/build_9e36a428ccb437382bf6801f5cf09071/staticfiles'.
remote:
remote: -----> Discovering process types
remote:        Procfile declares types -> web
...
# デプロイ成功
remote: Verifying deploy... done.
To https://git.heroku.com/<foo-bar-1234>.git
 * [new branch]      master -> master

 

Heroku上での作業

環境変数の確認

ON_HEROKU環境変数が読み込まれていることを確認します。

# 環境変数の確認
(env)$ heroku config

DATABASE_URL: <url>
ON_HEROKU:    yes

 
もし設定されていない場合は、以下の方法で設定します。

# 環境変数の追加
(env)$ heroku config:set ON_HEROKU=yes
Setting ON_HEROKU and restarting ⬢ <foo-bar-1234>.. done, v6
ON_HEROKU: yes

 

migrateとloaddata

Djangoアプリのmigrateとloaddataを行います。

# migrate
$ heroku run python manage.py migrate
Running python manage.py migrate on ⬢ <foo-bar-1234>... up, run.9469 (Free)
...

  Applying sessions.0001_initial... OK

# loaddata
$ heroku run python manage.py loaddata initial_data
Running python manage.py loaddata initial_data on ⬢ <foo-bar-1234>... up, run.8331 (Free)
Installed 2 object(s) from 1 fixture(s)

 

Herokuアプリの確認

ブラウザが開くので、動作を確認します。

# Herokuアプリを開く
$ heroku open

 

uWSGIでホストされてることを確認

Herokuのログを見て、uWSGIで動いていることを確認します。

# Herokuのログを見る
$ heroku logs
...
heroku[web.1]: Process exited with status 0
heroku[web.1]: Starting process with command `uwsgi uwsgi_heroku.ini`
app[web.1]: [uWSGI] getting INI configuration from uwsgi_heroku.ini
app[web.1]: *** Starting uWSGI 2.0.14 (64bit) on [xxx] ***
app[web.1]: compiled with version: 4.8.4 on xxx
app[web.1]: os: Linux-3.13.0-105-generic #152-Ubuntu SMP xxx
app[web.1]: machine: x86_64
app[web.1]: nodename: xxx
app[web.1]: clock source: unix
app[web.1]: detected number of CPU cores: 8
app[web.1]: pcre jit disabled
app[web.1]: current working directory: /app
app[web.1]: detected binary path: /app/.heroku/python/bin/uwsgi
app[web.1]: your processes number limit is 256
app[web.1]: your memory page size is 4096 bytes
app[web.1]: detected max file descriptor number: 10000
app[web.1]: lock engine: pthread robust mutexes
app[web.1]: thunder lock: disabled (you can enable it with --thunder-lock)
app[web.1]: uwsgi socket 0 bound to TCP address :xxx fd 3
app[web.1]: Python version: 3.5.2 (default, Jun 28 2016, 18:49:03)  [GCC 4.8.4]
app[web.1]: *** Python threads support is disabled. You can enable it with --enable-threads ***
app[web.1]: Python main interpreter initialized at 0x2854720
app[web.1]: your server socket listen backlog is limited to 100 connections
app[web.1]: your mercy for graceful operations on workers is 60 seconds
app[web.1]: mapped 145536 bytes (142 KB) for 1 cores
app[web.1]: *** Operational MODE: single process ***
app[web.1]: WSGI app 0 (mountpoint='') ready in 1 seconds on interpreter 0x2854720 pid: 4 (default app)
app[web.1]: *** uWSGI is running in multiple interpreter mode ***
app[web.1]: spawned uWSGI worker 1 (pid: 7, cores: 1)
app[web.1]: spawned uWSGI master process (pid: 4)
heroku[web.1]: State changed from starting to up
heroku[router]: at=info method=GET path="/" host=<foo-bar-1234>.herokuapp.com request_id=xxx fwd="xxx.xxx.xxx.xxx" dyno=web.1 connect=0ms service=88ms status=200 bytes=559
app[web.1]: {address space usage: 288747520 bytes/275MB} {rss usage: 30261248 bytes/28MB} [pid: 7|app: 0|req: 1/1] xxx.xxx.xxx.xxx () {48 vars in 896 bytes} [xxx] GET / => generated 471 bytes in 87 msecs (HTTP/1.1 200) 2 headers in 88 bytes (1 switches on core 0)
heroku[router]: at=info method=GET path="/static/image/shinanogold.png" host=<foo-bar-1234>.herokuapp.com request_id=xxx fwd="xxx.xxx.xxx.xxx" dyno=web.1 connect=0ms service=3ms status=200 bytes=5762
app[web.1]: {address space usage: 288747520 bytes/275MB} {rss usage: 30441472 bytes/29MB} [pid: 7|app: 0|req: 2/2] xxx.xxx.xxx.xxx () {50 vars in 985 bytes} [xxx] GET /static/image/shinanogold.png => generated 5584 bytes in 2 msecs via sendfile() (HTTP/1.1 200) 5 headers in 178 bytes (0 switches on core 0)

uWSGIで動いているようです。

 

ソースコード

GitHubにあげました。
thinkAmi-sandbox/Django_on_Heroku_uWSGI-sample