Flask+Python+Herokuで環境構築してから何かをデプロイするまで

### 0. はじめに

タイトルの通りです。

HerokuのPythonチュートリアル([https://devcenter.heroku.com/articles/getting-started-with-python](https://devcenter.heroku.com/articles/getting-started-with-python))はDjangoで作成したデモアプリのデプロイ方法を解説しています。

しかし, 学習コストの高いDjangoよりももっと手軽なFlaskでアプリを作りたかったので実際に試してみました。


参考記事:

https://tanuhack.com/python/deploy-flask-heroku/
https://qiita.com/ymgn_ll/items/96cac1dcf388bc7a8e4e

開発環境:

- MacOS ver 10.14.5
- Safari ver 12.1.1
- Homebrew ver 2.1.8
- Python ver 3.6.4
- Flask ver 1.1.1
- Werkzurg ver 0.15.5


### 1. Herokuのインストール, アカウント作成, ログイン

HomebrewでHerokuをインストールします。

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

Herokuでアカウントを作成しましょう。
(詳細は[https://tanuhack.com/python/deploy-flask-heroku/](https://tanuhack.com/python/deploy-flask-heroku/)などを参照してください)

作成が無事に終わったらHerokuにログインします。

```terminal
$ heroku login
```

すると,

```terminal
> heroku: Press any key to open up the browser to login or q to exit:
```

と指示されるので, `q`以外の任意のキーを押しましょう。
ブラウザが開いてHerokuのログイン画面に遷移します。

ログインが成功するとコマンドラインには以下のように表示されるはずです:

```terminal
> Opening browser to <URL_FOR_AUTHENTIFICATION>
> Logging in... done
> Logged in as <USERNAME>
```


### 2. 仮想環境の作成と立ち上げ

プロジェクト用のディレクトリを作成し, そこに移動しましょう:

```terminal
$ mkdir myproject
$ cd myproject

#   myproject    <- いまここ
```

この直下に`myenv`という別のディレクトリを作成し, そこに移動します:

```terminal
$ mkdir myenv
$ cd myenv

#   myproject
#   └ myenv    <- いまここ
```

当然, いまの時点で`myenv`の中身は空っぽです:

```terminal
$ ls  # 何も表示されない
```

ここで`python3 -m venv .`と打つと, `myenv`内部に仮想環境を立ち上げるための種々のファイルが自動的に作成されます:

```terminal
$ python3 -m venv .
$ ls
> bin         include     lib         pyvenv.cfg

#   myproject
#   └ myenv     <- いまここ
#     ├ bin
#     | ├ activate
#     |   ...
#     | 
#     ├ include
#     | ├ ...
#     |
#     ├ lib
#     | └ python3.6
#     |   └ ...
#     |
#     └ pyvenv.cfg
```

さっそくこのファイルを使って仮想環境内に入りましょう:

```terminal
$ source bin/activate
```

ターミナルの左端が`(仮想環境ファイルディレクトリ名)`となったら成功です:

```terminal
# myenv以下のファイルで構成された仮想環境内に入っている
(myenv) $ 
```

仮想環境から抜けるには`deactivate`と入力します:

```terminal
(myenv) $ deactivate
$  # 仮想環境から抜けた
```


### 3. パッケージの準備

ふたたび仮想環境に入ります:

```terminal
$ source bin/activate
```

仮想環境には最低限のパッケージしかインストールされていません。
そこでflaskとgunicornをインストールしておきましょう:

```terminal
(myenv) $ pip install flask gunicorn
```

### 4. ファイルの準備

次にFlaskアプリを動かすために必要なファイルを作成していきましょう。
作成するファイルは以下の3つです:

- hello.py
- requirements.txt
- Procfile


```:完成予想図
myproject
├ hello.py           (Flaskアプリ本体)
├ requirements.txt   (Herokuでの起動に必要)
├ Procfile           (Herokuでの起動に必要)
└ myenv     <- いまここ
  ├ bin
  ├ include
  ├ lib
  └ pyvenv.cfg
```


#### 4-1. hello.pyの作成 〜Flaskをローカルで動かそう〜
ローカルで動かないFlaskアプリはHerokuにデプロイしても動きません。
画面に Hello World! と表示するだけの簡単なアプリを作っていきましょう。

```terminal
(myenv) $ cd ..  # myproject内に移動
(myenv) $ touch hello.py
```

hello.pyには次のように記述します:

```Python3:hello.py
# -*- coding: utf-8 -*-
from flask import Flask

# 自分自身の名前をappという変数でインスタンス化
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

# コマンドラインで本ファイルを起動させたときの動作
if __name__ == '__main__':
    # 安全のため debug=False とする
    # 特に本番稼働するファイルでは debug=True としてはいけない!
    app.run(debug=False)
```

保存できたら, コマンドラインから`hello.py`を起動してみましょう:

```terminal
(myenv) $ python3 hello.py
```

コマンドラインには次のように表示されるはずです:

```
> * Serving Flask app "hello" (lazy loading)
> * Environment: production
      WARNING: This is a development server. Do not use it in a production deployment.
      Use a production WSGI server instead.
> * Debug mode: off
> * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
```

最後の行の`http://127.0.0.1:XXXX`の`XXXX`はポート番号を示しています。

ポート番号が5000番なら, ブラウザのURL欄に`localhost:5000`と入力しましょう。
ブラウザに Hello World! と表示されたら成功です!

コマンドラインで`Control+C`を入力すると, Flaskアプリを終了できます。





#### 4-2. Procfileの作成 〜Flaskをgunicornで動かそう〜

次のコマンドを打ってみてください:

```terminal
(myenv)$ gunicorn hello:app
```

このように表示されるはずです:

```terminal
[2019-07-28 15:07:00 +0900] [30377] [INFO] Starting gunicorn 19.9.0
[2019-07-28 15:07:00 +0900] [30377] [INFO] Listening at: http://127.0.0.1:8000 (30377)
[2019-07-28 15:07:00 +0900] [30377] [INFO] Using worker: sync
[2019-07-28 15:07:00 +0900] [30380] [INFO] Booting worker with pid: 30380
```

2行目に`http://127.0.0.1:8000`とありますね。
ここでブラウザのURL欄に`localhost:8000`と入力してみましょう。
やはりブラウザに Hello World! と表示されるはずです。

つまり, pythonファイルを直接起動するかわりに, gunicornコマンドを通じてFlaskアプリを起動させることもできるということです。

なぜわざわざこんな事をしたかというと, Herokuもまたgunicornコマンドを通じてアプリを起動するためです。
Herokuの挙動を追体験したわけですね。

実際のアプリ作成では, Herokuに実行してほしいgunicornコマンドはあらかじめ記述しておく必要があります。
そのファイルこそがProcfileです。

`myproject`ディレクトリ直下にProcfileを作成し, 

```
(myenv)$ touch Procfile
```

次のように編集します:

```:Procfile
web:gunicorn hello:app
```

これで, Herokuにアプリ起動指示を与えたときに`gunicorn hello:app`コマンドを通じてFlaskアプリを起動してくれるようになります。


#### 4-3. requirements.txt

ローカル環境で動いたアプリがきちんと他の環境でも動作するように, 必要なライブラリの種類やバージョンを列挙しておくファイルがrequirements.txtです。

これはコマンド一発で簡単に作成できます:

```terminal
(myenv)$ pip freeze > requirements.txt
```

こうするとHerokuはrequirements.txtの内容を読み取り, アプリ起動時に必要なライブラリを自動的にHeroku内部にインストールしてくれます。


### 5. Herokuにデプロイ

Herokuへのアプリのデプロイは, gitリポジトリをpushする形で行います。
つまり, Heroku上で編集作業を加えたり, Herokuに個々のファイルをアップロードしたりする作業は必要ないということです(便利!!)

GitHubに日頃から使い慣れている人ならとても簡単にアプリを公開することができます。

まずはアプリを格納したローカルリポジトリを作成しましょう。

```
(myenv) $ git init
(myenv) $ git add hello.py requirements.txt Procfile
(myenv) $ git commit -m 'My First Commit!'
```

ローカルリポジトリにはhello.py, requirements.txt, Procfileの3つのファイルが含まれていればOKです。

次に`heroku create`と入力しましょう:

```terminal
(myenv) $ heroku create
```

すると, Heroku上に空のリポジトリが作成されます。　

```
Creating app... done, ⬢ XXXX-YYYY-ZZZZ
https://XXXX-YYYY-ZZZZ.herokuapp.com/ | https://git.heroku.com/XXXX-YYYY-ZZZZ.git
```

`XXXX-YYYY-ZZZZ`の部分はHerokuが自動的に生成しますが, 自分の好きな名前に指定することもできます。その場合は `heroku create <好きなプロジェクト名>`のように入力しましょう。

たとえば`heroku create mywebapp`で作成したアプリのURLは`https://mywebapp.herokuapp.com`となります。


このHeroku上のリポジトリは, GitHub上のリモートと似ています。
PC上のローカルリポジトリをHeroku上にpushしたり, 逆にHerokuからローカル環境にpullしたりすることができるというわけです。

早速, ローカルリポジトリをHeroku上にpushしましょう。

```
(myenv) $ git push heroku master
```

これでHerokuへのアプリのデプロイ完了です!
あとは

```terminal
(myenv) $ heroku open
```
と打てば, Flaskアプリが起動し, ブラウザにHello World!と表示されるはずです。