Flask + Herokuでアプリを動かす


Flaskで作ったアプリを、いざ本番環境にデプロイしようと思ったのだけど。なかなか一筋縄でいかず、結局丸一日向き合うことになった。Railsでやってた時は、`heroku create`からの`git push heroku master`で瞬間に立ち上がっていたのに。

というわけで、FlaskをHerokuに立ち上げるまでを備忘録的に残していく。

## 開発環境
今回、デプロイにあたって使用した技術はこちら。

* Python 3.7.5
* Flask 1.1.1
* SQL Alchemy
* Flask Migrate

Flaskチュートリアルだと、`db.create_all()`コマンドでDBを作成している。しかし、いわゆるマイグレーションファイルに慣れている人からすると、やはりテーブルの状態管理をしておきたいもの。そういう場合には、`Flask Migrate`を使う。

## アプリの初期構築

まずは、アプリの初期構築をやっていく。
Flaskはかなりカスタマイズ度が高いため、開発者によってディレクトリ構成が異なる。まだ、ベストプラクティスは模索中なのだが、最終的にはこんな感じになった。

```
app/
├── manage.py
├── main
│   ├── __init__.py
│   ├── views.py
│   ├── config.py
│   ├── models.py
│   ├── app.db
│   ├── static/
│   ├── templates/
│   └── instance
│       └──application.cfg
├── Procfile
├── requirements.txt
├── migrations/
└── venv/
```

## プロジェクトの作成

```terminal
$ cd APP
$ mkdir -p main/{static,templates,instance}
$ cd main
$ touch manage.py requirements.txt Procfile
$ touch main/{__init__,views,models,config}.py
```

お好みでスタイルシートや、ビュー部分を足す。今回は、HerokuでDBがちゃんと動いて、「Hello world!」を出すまでをやるので、バックエンドのみ。

### manage.py
flaskを起動して実行するためのファイル。

```python:manage.py
from main import app
app.run()
```

### main/__init__.py
flaskアプリを生成。

```python:main/__init__.py
from flask import Flask

app = Flask(__name__)

import main.views
```

### main/views.py
Hello world!を表示するビュー部分。

```python:main/views.py
from flask import render_template
from main import app, db

@app.route('/')
def home():
    return render_template('home/index.html')
```

## 設定ファイルの作成

このあたりはベストプラクティスの記事がたくさん上がっているので、そちらを参考にしてみると分かりやすいかと。ここでは、一応動くと、秘密なものは隠せるレベルのものを紹介する。

### main/config.py
```python:main/config.py
import os

class BaseConfig(object):
    SQLALCHEMY_TRACK_MODIFICATIONS = False  

class DevelopmentConfig(BaseConfig):
    DEBUG = True
    TESTING = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///app.db'

class ProductionConfig(BaseConfig):
    DEBUG = True
    TESTING = True
    SQLALCHEMY_DATABASE_URI=os.environ.get('DATABASE_URL')

config = {
    "default": "main.config.BaseConfig",
    "development": "main.config.DevelopmentConfig",
    "production": "main.config.ProductionConfig",
}

def configure_app(app):
    config_name= os.getenv('FLASK_ENV')
    app.config.from_object(config[config_name])
    app.config.from_pyfile('application.cfg', silent=True)
```
**FLASK_ENV**は、`export FLASK_ENV=development`なりで設定しておく。ここで言ってるのは、基本BaseConfigの内容を共通で使いつつも、環境（development/production）によって、それぞれ変数の内容を分けているということ。

`os.environ.get('DATABASE_URL')`はHerkouで**DATABASE_UEL**を自動でデータベースに引っ張ってくれるため本番はこれで設定しておく。

### main/instance/application.cfg
```python:instance/application.cfg
SECRET_KEY='XXXXXXXXXXXXXXXXXXX'
```
ここでは、公にできないAPIキーを載せる。また、必ず`.gitignore`に記載して公開しないようにしておく。

### 設定ファイルを読み込む
用意した設定ファイルを`__init__.py`で使えるようにしておく。

```python:main/__init__.py
# 下記の内容を追記する
from main.config import configure_app

app = Flask(__name__, instance_relative_config=True)
configure_app(app)
```

## Herokuにデプロイ

### gunicornとpsycpg2のインストール
Herokuにデプロイ、`PostgreSQL`を使用するために2つのライブラリをインストールしておく。

```terminal
$ pip install gunicorm
$ pip install psycopg2
```

### Procfileに追記
herokuでアプリを動かすためのコマンドを書き足す。

```terminal
web: gunicorn main:app --log-file -
```

### インストールパッケージを用意
`pip freeze`は便利なコマンドで仮想環境中に使ってるライブラリ情報を全部記してくれる。

```terminal
$ pip freeze > requirements.txt
```

### Herokuアプリにデプロイ
herokuの設定は割愛する。

```terminal
$ heroku create flask-app
$ git push heroku master
$ heroku config:set FLASK_APP=main
$ heroku config:set FLASK_ENV=production
```

ここまでで、`$ heroku open`すれば「Hello world!」が表示されているはず。

## データベースの設定

### モデルの作成
今回は、テストとして`User`モデルを用意する。

```python:main/models.py
from main import db

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    email = db.Column(db.String, unique=True)

    def __repr__(self):
        return '<User %r>' % self.name
```

### Flask Migrate、SQL Alchemyのインストール
DBをマイグレートするために必要なライブラリをそれぞれインストールしていく。

```terminal
$ pip install flask_migrate
$ pip install flask_sqlalchemy
```

### モデルの読み込み
設定したモデルを読み込むための準備を`__init__.py`に書き足していく。

```python:main/__init__.py
# 下記を追記していく
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

db = SQLAlchemy(app)
migrate = Migrate(app, db)
db.init_app(app)

import main.models
```

### マイグレーション
Flask Migrateコマンドの順に沿ってマイグレーションしていく。

```terminal
$ flask db init
$ flask db migrate
$ flask db upgrade
```

これを実行すると、`migrations`ファイルと、`app.db`というデータベースが生成される。

## Herokuでマイグレーション

### インストールパッケージを用意
先ほどと同じように、新しくインストールしたライブラリをHerokuで使えるように設定していく。

```terminal
$ pip freeze > requirements.txt
```

### マイグレーション実行
ここが少しハマったのだが、Herokuにはローカルで生成した`migrations`ファイルごとサーバーにあげて、`db init`や`db migrate`は実行しない。

```terminal
$ heroku run flask db upgrade
```

## 参考文献

* [Flask チュートリアル](https://study-flask.readthedocs.io/ja/latest/02.html)
* [Flaskチュートリアル + herokuにデプロイ](https://qiita.com/yuucu/items/dea6e7a0a655af8fdb91)
* [Flask + SQLAlchemyプロジェクトを始める手順](https://qiita.com/shirakiya/items/0114d51e9c189658002e)
* [Flask database migrations on heroku](https://stackoverflow.com/questions/46430061/flask-database-migrations-on-heroku)