Pythonで始めるHeroku 2018

# はじめに
Pythonを使った簡単なWEBページを公開したいので，[Heroku](https://jp.heroku.com/)というものを触ってみることにした．

公式チュートリアル「[Getting Started on Heroku with Python | Heroku Dev Center](https://devcenter.heroku.com/articles/getting-started-with-python)」に従って作業を進める．基本的には自分用のメモ[^1]．

[^1]: チュートリアルをやった後に「[heroku で python 動かすチュートリアルをやったメモ - Qiita](https://qiita.com/cfiken/items/0715bb945389bc9ca682)」を読ませて頂いた．2016年1月の記事で，チュートリアルの流れは今も同じだが，細かいところが若干変更されているようである．

### 作業前のローカル環境

* MacOSX 10.11.6
* Python 3.6.3 
* Git 2.10.1

### 作業者について

* Pythonは多少使っているが，Rの方が得意
* PostgresもDjangoもよくわかってない（使ったことない）
* 実はGitも怪しい
* 英語を読むのは苦にならない

### 注意事項

* 表示されるメッセージは，ある程度編集した
* あと，何か気づいたら書く

# 作業概要
[公式チュートリアル](https://devcenter.heroku.com/articles/getting-started-with-python)は次のような手順になっている．

1. [導入](https://devcenter.heroku.com/articles/getting-started-with-python#introduction)
2. [セットアップ](https://devcenter.heroku.com/articles/getting-started-with-python#set-up)
3. [デモアプリを準備する](https://devcenter.heroku.com/articles/getting-started-with-python#prepare-the-app)
4. [デモアプリをデプロイする](https://devcenter.heroku.com/articles/getting-started-with-python#deploy-the-app)
5. [ログを確認する](https://devcenter.heroku.com/articles/getting-started-with-python#view-logs)
6. [Procfileを定義する](https://devcenter.heroku.com/articles/getting-started-with-python#define-a-procfile)
7. [デモアプリをスケールする](https://devcenter.heroku.com/articles/getting-started-with-python#scale-the-app)
8. [依存関係を宣言する](https://devcenter.heroku.com/articles/getting-started-with-python#declare-app-dependencies)
9. [ローカルでデモアプリを動かす](https://devcenter.heroku.com/articles/getting-started-with-python#run-the-app-locally)
10. [ローカルでの変更をpushする](https://devcenter.heroku.com/articles/getting-started-with-python#push-local-changes)
11. [アドオンをプロビジョニングする](https://devcenter.heroku.com/articles/getting-started-with-python#provision-add-ons)
12. [コンソールを開く](https://devcenter.heroku.com/articles/getting-started-with-python#start-a-console)
13. [config varsを定義する](https://devcenter.heroku.com/articles/getting-started-with-python#define-config-vars)
14. [データベースをプロビジョニングする](https://devcenter.heroku.com/articles/getting-started-with-python#provision-a-database)

# 1. [導入](https://devcenter.heroku.com/articles/getting-started-with-python#introduction)
まずは下記の状態を目指す．

* Herokuの無料アカウントがある
* Python 3.6以上がローカルにインストールされている
* Pipenvがローカルにインストールされている
* Postgresがローカルにインストールされている

Python 3.6以上については，元々あるので作業しない．

## 1.1. Herokuの無料アカウントを作成する
メールアドレスがあればOK．さすがにつまづくところはない．

## 1.2. Pipenvをインストールする

```
$ pip install pipenv
```

## 1.3. Postgresをインストールする
ローカルでアプリを動かすのに必要．ちょっとだけ面倒くさい．

[Postgres.app – the easiest way to get started with PostgreSQL on the Mac](https://postgresapp.com/)を参照しながら実施してみる．

まず，Postgres.appからダウンロードして，Applicationにインストールする．起動したらInitializeボタンをクリックして，新しくサーバーを作成する．

次にパスを通す．

```
$ sudo mkdir -p /etc/paths.d &&
$ echo /Applications/Postgres.app/Contents/Versions/latest/bin | sudo tee /etc/paths.d/postgresapp
```

最後に，環境変数`DATABASE_URL`を設定する．

```
$ export DATABASE_URL=postgres://($whoami)
```

# 2. [セットアップ](https://devcenter.heroku.com/articles/getting-started-with-python#set-up)

Gitが無い場合は，まずGitをインストールしてセットアップする．あるのでこの作業はしない．

満を持してHeroku CLIをインストールする．インストーラーをダウンロードすることもできるが，面倒なのでHomebrewを使おう．

```
$ brew install heroku/brew/heroku
```

と思ったら何やらエラーが出たので，やっぱりインストーラーを使ってインストール．

その後，ターミナルからログインする．

```
$ heroku login
```
これで，Herokuの無料アカウント作成時に使ったメールアドレスとパスワードを入力すると， 

```
Logged in as メールアドレス
```

と表示される．（もちろん，表示されるメールアドレスは実際のもの）

# 3. [デモアプリを準備する](https://devcenter.heroku.com/articles/getting-started-with-python#prepare-the-app)
Herokuが用意しているサンプルアプリを適当なディレクトリでcloneする．

```
$ git clone https://github.com/heroku/python-getting-started.git
$ cd python-getting-started
```

ちなみに中身はこんな感じ．

```
$ ls
Pipfile			Procfile.windows	gettingstarted
Pipfile.lock	README.md			hello
Procfile		app.json			manage.py
```
# 4. [デモアプリをデプロイする](https://devcenter.heroku.com/articles/getting-started-with-python#deploy-the-app)

まず，Heroku上にアプリを作成する．

```
$ heroku create
Creating app... done, ⬢ lit-bastion-5032
https://lit-bastion-5032.herokuapp.com/ | https://git.heroku.com/lit-bastion-5032.git
```
`lit-bastion-5032`の部分はHerokuによってランダムに付けられる名前．自分でつけることもできるらしいけど，どうやるのかチュートリアルではわからない[^3]．

[^3]: `heroku create myapp`で，名前が重複していなければ「myapp」というアプリができる．

で，pushする．

```
$ git push heroku master
Counting objects: 232, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (217/217), done.
Writing objects: 100% (232/232), 29.64 KiB | 0 bytes/s, done.
Total 232 (delta 118), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Python app detected
remote: -----> Installing python-3.6.0
remote: -----> Installing requirements with latest pipenv...
remote:        Installing dependencies from Pipfile.lock...
remote:      $ python manage.py collectstatic --noinput
remote:        58 static files copied to '/app/gettingstarted/staticfiles', 58 post-processed.
remote:
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote:
remote: -----> Compressing...
remote:        Done: 39.3M
remote: -----> Launching...
remote:        Released v4
remote:        http://lit-bastion-5032.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To git@heroku.com:lit-bastion-5032.git
 * [new branch]      master -> master
```

上のメッセージは実際のものではなくチュートリアルのコピーで，大体こんな感じで終わる．

次に，インスタンスを１つ動かす．

```
$ heroku ps:scale web=1
Scaling dynos... done, now running web at 1:Free
```
ブラウザでアプリのURLを直接開くか，ターミナルで

```
$ heroku open
```

とやると，アプリが表示される．
![20180816_HerokuSampleApp.png](https://qiita-image-store.s3.amazonaws.com/0/157373/372e8a7f-ca94-391d-616c-77d763e97528.png)
デプロイ成功！

# 5. [ログを確認する](https://devcenter.heroku.com/articles/getting-started-with-python#view-logs)

Herokuでは，全てのアプリとHerokuコンポーネントの出力を時系列のイベントにまとめて，1つのチャネルで確認することができる．

```
$ heroku logs --tail
```
とやると，ログが色々と表示される．WEBアプリを再表示すると，新たなイベントが追加表示される．ログの意味は今は気にしない．

`Control+C`でストリーミングを中断する．

# 6. [Procfileを定義する](https://devcenter.heroku.com/articles/getting-started-with-python#define-a-procfile)

Procfileは，アプリを実行する際にどのコマンドが実行されるかを明示するためのファイル．「定義する」と言いつつ，今は中身を見て，Procfileが何かを（ある程度）理解するだけ．

```
$ more Procfile
web: gunicorn gettingstarted.wsgi
```
チュートリアルでは末尾に`--log-file -`が付いているが，気にしない．

先頭の`web`というのが重要で，これを宣言すると，このプロセスはHerokuのHTTP routing stacksにアタッチされ，デプロイされるとWebトラフィックを受け取る．

それ以降はコマンドで，ここではgunicornを起動している．

# 7. [デモアプリをスケールする](https://devcenter.heroku.com/articles/getting-started-with-python#scale-the-app)
「[dyno](https://jp.heroku.com/dynos)」の数を変えるだけ．dynoは軽量のコンテナだと思えば良い．

## 7.1. dynoの数を確認する
現時点で，サンプルアプリは1つのdyno上で動いている．`ps`コマンドで，dynoが幾つ動いているか確認できる．

```
$ heroku ps
Free dyno hours quota remaining this month: 550h 0m (100%)
For more information on dyno sleeping and how to upgrade, see:
https://devcenter.heroku.com/articles/dyno-sleeping

=== web (Free): gunicorn gettingstarted.wsgi (1)
web.1: up 2018/08/xx xx:xx:xx +0900 (~ xxm ago)
```

これは，dynoが1つということ．

デフォルトだと無料のdynoが使われる．無料のdynoは，30分間何のトラフィックも無いとスリープする．また，1ヶ月の時間割当があって，割当を使い果たすまでは動き続ける．

## 7.2. dynoの数を変える

スケールするとは，dynoの数を変えるということ．ただし，2つ以上のdynoを動かすにはアカウントのverificationが必要（クレジットカード登録が必要）なので，とりあえずチュートリアルでは数を1から0に変更する．

```
$ heroku ps:scale web=0
Scaling dynos... done, now running web at 0:Free
```
この状態でアプリにアクセスすると，エラーが表示される．

![20180816_HerokuSampleError.png](https://qiita-image-store.s3.amazonaws.com/0/157373/f544774f-d719-6a3d-4691-14ec801ba3bf.png)

スケールアップするには，再び数を1にする．

```
$ heroku ps:scale web=1
Scaling dynos... done, now running web at 1:Free
```
もちろん，この状態ならアプリにアクセスしてもエラーは表示されない．

# 8. [依存関係を宣言する](https://devcenter.heroku.com/articles/getting-started-with-python#declare-app-dependencies)
ローカルでPipenvを使う話．

## 8.1. Heroku上の話（読むだけ）
Herokuは，rootに存在する「Pipfile」または「requirements.txt」によって，アプリがPythonアプリであることを認識する．

デモアプリのPipfileの中身はこんな感じ．

```
$ more Pipfile
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true

[packages]
django = "*"
gunicorn = "*"
django-heroku = "*"

[requires]
python_version = "3.6"
```
Heroku上にアプリがデプロイされると，Herokuはこのファイルを読んで，`pipenv install --system --skip-lock`コマンドを使い，適切な依存関係をインストールする，らしい．

## 8.2. ローカルで実施する

Pipenvを使ってvirtualenvを作り，依存関係をインストールする．これを行うためにはPostgresがちゃんとインストールされてないとダメなので注意[^2]．

[^2]: と書いてあるが，ここPostgres関係ある？

```
$ pipenv --three
$ pipenv install
```
問題なければ，virtualenvをアクティベートして，仮想環境を有効にする．

```
$ pipenv shell
```
# 9. [ローカルでデモアプリを動かす](https://devcenter.heroku.com/articles/getting-started-with-python#run-the-app-locally)

まずはDjangoのためにcollectstaticを走らせる．

```
$ python manage.py collectstatic
```

チュートリアルには「yesと答えろ」とか書いてあるけど何も聞かれない．気にしない．

次に，アプリを起動する．

```
$ heroku local web
```
ここでこんなウィンドウが表示された．「許可」をクリック．
![20180817_warning.png](https://qiita-image-store.s3.amazonaws.com/0/157373/42d13c63-2140-30a7-f801-7049fa509b12.png)
ブラウザで[http://localhost:5000]([http://localhost:5000])を開くと，Heroku上と同じようにデモアプリが表示される．
![20180816_HerokuSampleApp.png](https://qiita-image-store.s3.amazonaws.com/0/157373/6f2f27b4-9f99-c4d5-012d-bd5b85b83189.png)
ローカルでデモアプリが起動できたことになる．ターミナルで`Ctrl+C`を押すとプロセス中断．

# 10. [ローカルでの変更をpushする](https://devcenter.heroku.com/articles/getting-started-with-python#push-local-changes)
ここでは，ローカルでのデモアプリへの変更を，Heroku上に反映させる．

## 10.1. requestsをインストールする
requestsを新たに利用するために，ローカルにインストールしてみる．

```
$ pipenv install requests
```
そうすると，Pipfileの内容が変更され，requestsが追加されているのがわかる．

```
$ more Pipfile
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true

[packages]
django = "*"
gunicorn = "*"
django-heroku = "*"
requests = "*"

[requires]
python_version = "3.6"
```

## 10.2. Pythonコードを変更する
requestsを使うようにPythonコードを変更する．変更するファイルは`hello/views.py`．

先頭に以下を追加する．

```python:views.py
import requests
```

さらに，indexメソッドの中身を，requestsを使う以下のものに書き換える．

```python:views.py
def index(request):
    r = requests.get('http://httpbin.org/status/418')
    print(r.text)
    return HttpResponse('<pre>' + r.text + '</pre>')
```

## 10.3. ローカルで起動する
views.pyを書き換えたら，ローカルでデモアプリを起動してみる．

```
$ heroku local
```
ファイルの確認や編集のためにディレクトリを移動していたら，`python-getting-started`に戻ること．

先刻は`heroku local`の後ろに`web`を付けたけど，この時点でProcfileにはwebが1つ定義されているだけなので，付けても付けなくても同じことのようだ．

再びブラウザで[http://localhost:5000]([http://localhost:5000])を開くと，[http://httpbin.org/status/418](http://httpbin.org/status/418)の出力である，変なAAが表示される．

![20180817_teapot.png](https://qiita-image-store.s3.amazonaws.com/0/157373/e6f559f7-61c0-320c-1feb-23e924c83372.png)

公式チュートリアルには「可愛いティーポット（a lovely teapot）」と書いてあるが，どうしても顔が見える．

## 10.4. デプロイする
ローカルでの変更を，Heroku上に反映させる．Herokuにおけるほとんど全てのデプロイは，同じ手順を取るらしい．

まずはローカルのgitレポジトリに修正されたファイルを追加する．

```
$ git add .
```
次に変更をコミットする．

```
$ git commit -m "Demo"
```

前に一度やったのと同じように，デプロイする．

```
$ git push heroku master
```

で，Heroku上のデモアプリを表示すると

```
$ heroku open
```

ローカルと同じく，変なAA……じゃなくて，可愛いティーポットが表示される．
![20180817_teapot.png](https://qiita-image-store.s3.amazonaws.com/0/157373/e6f559f7-61c0-320c-1feb-23e924c83372.png)
ローカルでの変更が，Heroku上に反映されたことがわかる．

# 11. [アドオンをプロビジョニングする](https://devcenter.heroku.com/articles/getting-started-with-python#provision-add-ons)
Herokuにはサードパーティのアドオンがある．ここでは，[Papertrail](https://devcenter.heroku.com/articles/papertrail)と言うログ統合管理のアドオンをプロビジョニングする．

```
$ heroku addons:create papertrail
Creating papertrail on ⬢ lit-bastion-5032... !
 ▸    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

```

上にある通りアカウントのベリフィケーションが必要なので，ここはスキップしてしまおう．

ベリフィケーションが終わっていれば，このコマンドだけでアドオンのデプロイができていたらしい．

追加したアドオンをリストアップする．（未確認）

```
$ heroku addons
```

PapertrailのWEBコンソールを表示する．（未確認）

```
$ heroku addons:open papertrail
```

# 12. [コンソールを開く](https://devcenter.heroku.com/articles/getting-started-with-python#start-a-console)
`heroku run`コマンドを使うと，[one-off dyno](https://devcenter.heroku.com/articles/one-off-dynos)上でコマンドを走らせることができる．ここでの「one-off」とは，一回限りと言う意味．

## 12.1. Pythonの対話型インタプリタを開く

```
$ heroku run python manage.py shell
Running python manage.py shell on ⬢ lit-bastion-5032... up, run.1060 (Free)
/app/.heroku/python/lib/python3.6/site-packages/psycopg2/__init__.py:144: UserWarning: The psycopg2 wheel package will be renamed from release 2.8; in order to keep installing from binary please use "pip install psycopg2-binary" instead. For details see: <http://initd.org/psycopg/docs/install.html#binary-install-from-pypi>.
  """)
Python 3.6.6 (default, Aug  1 2018, 21:10:27) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole) >>>
```
このPythonシェルは，デモアプリと同じ環境で動いている．例えば requestsを使って変なAA……じゃなくて可愛いティーポットを表示できる（しつこい）．

```
>>> import requests
>>> print(requests.get('http://httpbin.org/status/418').text)

    -=[ teapot ]=-

       _...._
     .'  _ _ `.
    | ."` ^ `". _,
    \_;`"---"`|//
      |       ;/
      \_     _/
        `"""`
>>> exit()
```

## 12.2. bashもいける

```
$ heroku run bash
Running bash on ⬢ lit-bastion-5032... up, run.8093 (Free)
~ $ ls
app.json		manage.py     Procfile			requirements.txt
gettingstarted	Pipfile       Procfile.windows	runtime.txt
hello			Pipfile.lock  README.md			staticfiles
~ $ exit
```
one-off dynoはそれぞれのephemeralな（一時的な）ファイルシステムを持ち，接続が切れると消える．よって，例えばここでファイルを削除したとしても，アプリに影響を及ぼすことはない．

最後にexitしてdynoを止めるのを忘れないように．

# 13. [config varsを定義する](https://devcenter.heroku.com/articles/getting-started-with-python#define-config-vars)
外部リソースのアドレスとか暗号化キーなど，永続的に保持しておきたい設定はconfig varsに保存できる．

config varsは，起動しているアプリに対して環境変数として示される．

## 13.1. Pythonコードを変更する

またもや`hello/views.py`を変更する．先頭に下記を追加する．

```python:views.py
import os
```

さらに（またもや）indexメソッドを書き換える．環境変数`TIMES`を使っている．

```python:views.py
def index(request):
    times = int(os.environ.get('TIMES',3))
    return HttpResponse('Hello! ' * times)
```
`os.environ.get()`の第2引数(3)は，`TIMES`がなかったときのデフォルト設定なので気にしない．

## 13.2. .envを確認する
デモアプリには「.env」というファイルが存在し，既にTIMES環境変数が定義されている．`heroku local`コマンドは，このファイルを参照して環境を設定する．

```
$ ls .env
.env
$ more .env
TIMES=2
```

この状態でローカルでアプリを起動する．

```
$ heroku local
```

[http://localhost:5000](http://localhost:5000)をブラウザで開くと，「Hello! Hello!」と2回出力されていることが確認できる．

## 13.3. Heroku上のconfig varを設定する

Heroku上のconfig varは，次のようにして設定することができる．公式チュートリアルだと`TIMES=2`で.envと同じで面白くないので，`TIMES=4`にしてみる．

```
$ heroku config:set TIMES=4
Setting TIMES and restarting ⬢ lit-bastion-5032... done, v7
TIMES: 4
```

設定されているconfig varは，次のようにして確認できる．

```
$ heroku config
=== lit-bastion-5032 Config Vars
DATABASE_URL: postgres://xxx
TIMES:        4
```

この環境変数がちゃんと取り込めるか，デプロイして確認してみる．

```
$ git add .
$ git commit -m "Demo"
$ git push heroku master
$ heroku open
```

ブラウザで，「Hello! Hello! Hello! Hello!」と4回表示される．

# 14. [データベースをプロビジョニングする](https://devcenter.heroku.com/articles/getting-started-with-python#provision-a-database)

Herokuではデータベースはアドオンで，RedisとかMongoDBとかMySQLとか色々あるけれど，アプリがデプロイされると，無料のHeroku Postgresが自動的にプロビジョニングされる．ここではこのPostgresについて学ぶ．

## 14.1. Postgresの情報を確認する
データベースはアドオンなので，次のコマンドで確認できる．

```
$ heroku addons

Add-on                                             Plan       Price  State  
─────────────────────────────────────────────────  ─────────  ─────  ───────
heroku-postgresql (postgresql-asymmetrical-xxxxx)  hobby-dev  free   created
 └─ as DATABASE

The table above shows add-ons and the attachments to the current app (lit-bastion-5032) or other apps.
```

環境変数`DATABASE_URL`には，URLが設定されている．次のコマンドで確認できる．

```
$ heroku config
=== lit-bastion-5032 Config Vars
DATABASE_URL: postgres://xxx
TIMES:        4
```

`pg`コマンドでもっと詳細な情報を確認できる．

```
$ heroku pg
=== DATABASE_URL
Plan:                  Hobby-dev
Status:                Available
Connections:           0/20
PG Version:            10.4
Created:               2018-08-xx xx:xx UTC
Data Size:             7.7 MB
Tables:                0
Rows:                  0/10000 (In compliance)
Fork/Follow:           Unsupported
Rollback:              Unsupported
Continuous Protection: Off
Add-on:                postgresql-asymmetrical-xxxxx
```

## 14.2. テーブルを作成する
デモアプリのURLの後ろに`/db`を追加すると，データベースの機能にアクセスできるが，テーブルが無いのでエラーが表示される．

次のコマンドで標準的なDjangoを走らせてテーブルを作成する．

```
$ heroku run python manage.py migrate
```
チュートリアルには，もし，次のようなメッセージが表示されたら「no」と答えろと書いてあるが，聞かれなかった．

"You just installed Django’s auth system, which means you don’t have any superusers defined. Would you like to create one now?"

これでもう一度，`/db`を付けたURLにアクセスすると，「Page View Report」が表示される．

データベースへアクセスするコードは`hello/models.py`に書いてある．GreetingsというシンプルなDjangoモデルを使っている．読むだけ．

また，環境変数`DATABASE_URL`に基づいてデータベースのセットアップを行うための設定は，`gettingstarted/settings.py`に記述してある．中身を確認するだけ．

## 14.3. views.pyのdbメソッドを確認する
`/db`を付けたURLにアクセスすると`hello/views.py`のdbメソッドが呼ばれている．dbメソッドは，新たなGreetingを作成し，存在する全てのGreetingをrenderメソッドに渡す．

中身を確認する．

```python
def db(request):

    greeting = Greeting()
    greeting.save()

    greetings = Greeting.objects.all()

    return render(request, 'db.html', {'greetings': greetings})
```

## 14.4. リモートのデータベースにアクセスする
ローカルにPostgresがインストールされていれば，次のコマンドでリモートのデータベースにアクセスできる．

```
$　heroku pg:psql
--> Connecting to postgresql-asymmetrical-xxxxx
psql (10.5, server 10.4 (Ubuntu 10.4-2.pgdg14.04+1))
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

lit-bastion-5032::DATABASE=> 
```
SQLを使ってみる．

```
lit-bastion-5032::DATABASE=> select * from hello_greeting;
 id |             when              
----+-------------------------------
  1 | 2018-08-xx xx:xx:xx.xxxxxx+00
  2 | 2018-08-xx xx:xx:xx.xxxxxx+00
(2 rows)
```

`/db`へのアクセスが表示される．

接続を切るには，

```
lit-bastion-5032::DATABASE=> \q
```
でOK．

# おわりに
作業は以上で終了である．大きくつまづくところはなかったが，意味がわかっていない部分は多々ある．

チュートリアルは，[Next Steps](https://devcenter.heroku.com/articles/getting-started-with-python#next-steps)として，次の3つのページを紹介している．

* [How Heroku Works](https://devcenter.heroku.com/articles/how-heroku-works)
* [Deploying Python and Django Apps](https://devcenter.heroku.com/articles/deploying-python)
* [Python category](https://devcenter.heroku.com/categories/python-support)

ということで，勉強は続く．