Visual Studioで作ったFlaskアプリをGitHub経由でHerokuにデプロイする方法

タイトルの環境でのデプロイではまったので注意すべき点を記事にします。正直試行錯誤の末ようやくうまくいったという感じなので、理屈がわかっていない部分は結構ありますが参考になればと思います。もしも間違っている箇所があれば教えて頂けると大変助かります。

# 動機
Pythonで作った簡単なWebアプリケーションをHerokuにデプロイしたかったのですが、__Visual Studioならテンプレートを選ぶだけでFlaskアプリが作れるし、GUIでGitHubと連携できる！さらにGitHubとHerokuも連携できるので、この組み合わせが楽なのでは？__と思いやってみました（が、冒頭の通り思いのほか苦戦しました）

# 前提
* HerokuやGitHubのアカウントは作成済
* Visual Studioインストール済（今回はVisual Studio Community 2019 Version 16.9.5を利用しました）

# Herokuにデプロイするまでの手順
## 1. Visual StudioでFlask Web プロジェクトを作る
特に難しいことはなく`Flask Web プロジェクト`を選択するだけです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337041/b38bdcd3-829a-85c1-1188-425d26b1daf3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337041/54733dca-a7a1-4f5e-6bea-9fa165e5761e.png)


## 2. Herokuへのデプロイに必要なファイルの作成
HerokuにデプロイするためにはProcfile、requirements.txt、runtime.txtの3ファイルが必要になります。作り方は以下の通りです。

### 2-1. Procfileの作成
ProcfileはHerokuに起動時に実行するコマンドを指定するためのもので必ず必要なファイルです。また、__ルートディレクトリに配置しないと動きません！__（これではまった人は多そう）詳細は[公式](https://devcenter.heroku.com/ja/articles/procfile)を参照下さい。

色々なサイトを見ていると、

``` Procfile
web: gunicorn FlaskOnHeroku.views:app --log-file -
```

とgunicornコマンドを利用するパターンと、

``` Procfile
web: python FlaskOnHeroku/views.py
```

とpythonコマンドを利用するパターンのどちらも書かれていましたが、今回は__前者でないとうまく動きませんでした__（エラー詳細は[こちら](#procfileにpythonコマンドを書いたときのエラー)）。理由はよくわかりません・・・（ご存じの方がいれば教えて下さい）。
もうひとつの注意点ですが、gunicornコマンドを利用する場合、__ディレクトリの区切りはピリオド__になります！（この情報はなかなか見つけられずはまりました）上の例だとFlaskOnHerokuがディレクトリ名、viewsがviews.pyの拡張子を取ったものになります（拡張子は含めず書くようです）。ちなみに`app`は\_\_init\_\_.pyの中の`app = Flask(__name__)`と対応しているはずなので、もしも別の変数名を使っていればそれに合わせて下さい。

### 2-2. requirements.txtの変更
このファイルに必要なパッケージを記述することで、デプロイ時にHerokuがこれらをインストールしてくれます。おそらくVisual Studioがrequirements.txtを初めから作ってくれていると思いますが（下の例の一行目のみ書かれている）、これにgunicornを追加します。

``` requirements.txt
Flask~=1.1
gunicorn
```

他のサイトを見ていると、以下のようにローカルで利用しているパッケージと同じものをHerokuに導入しようとしていました。今回のサンプルアプリはHello Worldに近いものなので、これをスキップしても問題なかったのですが、今後本格的なアプリを作る際には必要になりそうです。

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

### 2-3. runtime.txtの作成
ここに実行環境のバージョンを指定します。今回は以下のように書きましたが、各自の環境に応じて変更下さい。

``` runtime.txt
python-3.7.8
```

### 2-4. 最終的なディレクトリ構成
次のようになります。Procfile、requirements.txt、runtime.txtがプロジェクトの直下にあり、動かしたいviews.pyはFlaskOnHerokuディレクトリ下にあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337041/330dcd49-6fa5-f046-8be7-b785906a1b51.png)

## 3. GitHubにプッシュ
Visual Studioのソリューションエクスプローラーの右側のGitのタブから、コミット、GitHubへプッシュします。特に難しくないと思いますので詳細割愛します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337041/f23d7a49-5f94-543b-610d-9924ab44022a.png)

## 4. Herokuアプリケーションの追加
### 4-1. GitHubとの連携
HerokuのWebページから`Create new app`を選択し、アプリ名を決めます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337041/5d1b5d4b-5f87-fc0a-5e7f-fd4c3f27153e.png)

次にDeployment methodを`GitHub`に変更し、連携するGitHubのリポジトリ名を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337041/8cea791f-628b-13c8-acd8-4024adfe7b0b.png)

Automatic deploysの`Enable Automatic deploys`をクリックし、GitHubのコードに変更がある都度自動デプロイするよう設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337041/88841ba0-e8fc-1e38-5cc5-2b44dd27fd22.png)

### 4-2. Setting変更
ここもはまったポイントなのですが、GitHubのルートディレクトリは次のようになっているはずです。ただ、Herokuにデプロイしたいのは、FlaskOnHerokuディレクトリ以下です。ProcfileなどはHerokuのルートディレクトリ下にないとダメなため、このまま全体をデプロイしてもエラーになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337041/605bed45-1638-0f6f-ed3e-5a05fe84bc7c.png)

こういったサブディレクトリをデプロイ対象として選択するためには、heroku-buildpack-monorepoというbuildpackを使うことになります。これをSettingタブから導入します。Buildpacksの`Add buildpack`を押し、`https://github.com/lstoll/heroku-buildpack-monorepo`を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337041/fe688c43-96bb-f514-864c-c52c64acadc6.png)

次にheroku/pythonも追加します。これは下の`python`を押せばOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337041/0029d16d-b457-7f4c-9edb-98c571ff2fba.png)

また、Config Varsの`Reveal Config Vars`を押し、KEYに`APP_BASE`を、VALUEに`FlaskOnHeroku`（サブディレクトリ名）を入力します。これによりデプロイ対象ディレクトリを指定しています。

最終的には以下のようになります。heroku-buildpack-monorepoとheroku/pythonの順番はこの通りでないとダメですので注意下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337041/5509f9d7-e3a9-e444-78f2-77a704d979cb.png)

### 4-3. 手動デプロイ、確認
最後に初回デプロイのためDeployタブに戻り、Manual deployの`Deploy Branch`をクリックします。デプロイが終われば`View`ボタンが表示されますので、これをクリックし（右上の`Open app`ボタンでも可）デプロイされたアプリを確認し終了です。

### 【補足】heroku-buildpack-monorepoを使わない方法
今回、heroku-buildpack-monorepoを使うことになったのは、プロジェクトの上のディレクトリ階層にソリューションファイルがあったためです。そのため、Visual Studioで新しくプロジェクトを作るときに次のようにソリューションとプロジェクトを同じディレクトリに配置することでheroku-buildpack-monorepoを使う必要はなくなります。Buildpacksにheroku/pythonを追加する必要もないので、こちらの方が楽かもしれません（背反は不明ですが、1ソリューションで複数のプロジェクトを管理する場合には困るかもしれません）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337041/f346cae0-bc17-2b65-329f-08d0a1f14123.png)

# はまったときには？
今回、数多くはまった経験より、はまったときにはログを確認するのがいいと思いました（当たり前ですが）。BuildログはActivityタブから確認できます。また、より詳細なログを確認するには、コマンドプロンプトから以下のコマンドを打つといいです
（事前に[Heroku CLI](https://devcenter.heroku.com/ja/articles/heroku-cli)のインストールが必要なはず）。flask-on-herokuはHerokuのアプリ名なため適宜変更下さい。ログは全リリースつながった内容になっているので、関係のある部分（基本的には最新のリリース）のみ参照するようにして下さい。

```
heroku logs -a=flask-on-heroku
```

# コード
以下に公開しています。
* https://github.com/shoji9x9/FlaskOnHeroku
* https://github.com/shoji9x9/FlaskOnHeroku2 ：ソリューションとプロジェクトを同じディレクトリに配置したバージョン

# Tips：Herokuアプリケーションの停止
無料でHerokuを使っている場合、30分でスリープするそうですが、手動で停止したい場合はResourcesタブから停止できます。同じ要領で再開もできます（つまみを右にすると動き、左にすると止まります）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337041/3e73a35f-18b3-2391-a108-da1311a63aeb.png)

#Procfileにpythonコマンドを書いたときのエラー
Procfileにpythonコマンドを書いた場合、ログに以下のエラーが出ていました。views.pyの中の`@app.route('/home')`がまずいようです。\_\_init\_\_.pyの中の`app = Flask(__name__)`をviews.pyに移植することで改善しそうな気もしたのですがうまくはいかず、それ以上の調査は断念しました。

```
2021-07-08T00:29:42.084171+00:00 heroku[web.1]: Restarting
2021-07-08T00:29:42.148384+00:00 heroku[web.1]: State changed from up to starting
2021-07-08T00:29:44.404543+00:00 heroku[web.1]: Stopping all processes with SIGTERM
2021-07-08T00:29:44.523881+00:00 app[web.1]: [2021-07-08 00:29:44 +0000] [9] [INFO] Worker exiting (pid: 9)
2021-07-08T00:29:44.525208+00:00 app[web.1]: [2021-07-08 00:29:44 +0000] [10] [INFO] Worker exiting (pid: 10)
2021-07-08T00:29:44.525880+00:00 app[web.1]: [2021-07-08 00:29:44 +0000] [4] [INFO] Handling signal: term
2021-07-08T00:29:44.532266+00:00 app[web.1]: [2021-07-08 00:29:44 +0000] [4] [WARNING] Worker with pid 9 was terminated due to signal 15
2021-07-08T00:29:44.535449+00:00 app[web.1]: [2021-07-08 00:29:44 +0000] [4] [WARNING] Worker with pid 10 was terminated due to signal 15
2021-07-08T00:29:44.627895+00:00 app[web.1]: [2021-07-08 00:29:44 +0000] [4] [INFO] Shutting down: Master
2021-07-08T00:29:44.740145+00:00 heroku[web.1]: Process exited with status 0
2021-07-08T00:29:45.711143+00:00 heroku[web.1]: Starting process with command `python FlaskOnHeroku2/views.py`
2021-07-08T00:29:48.566986+00:00 app[web.1]: Traceback (most recent call last):
2021-07-08T00:29:48.567009+00:00 app[web.1]: File "FlaskOnHeroku2/views.py", line 10, in <module>
2021-07-08T00:29:48.567325+00:00 app[web.1]: @app.route('/home')
2021-07-08T00:29:48.567359+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.7/site-packages/flask/app.py", line 1315, in decorator
2021-07-08T00:29:48.568276+00:00 app[web.1]: self.add_url_rule(rule, endpoint, f, **options)
2021-07-08T00:29:48.568319+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.7/site-packages/flask/app.py", line 98, in wrapper_func
2021-07-08T00:29:48.568537+00:00 app[web.1]: return f(self, *args, **kwargs)
2021-07-08T00:29:48.568567+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.7/site-packages/flask/app.py", line 1284, in add_url_rule
2021-07-08T00:29:48.569459+00:00 app[web.1]: "existing endpoint function: %s" % endpoint
2021-07-08T00:29:48.569584+00:00 app[web.1]: AssertionError: View function mapping is overwriting an existing endpoint function: home
2021-07-08T00:29:48.647837+00:00 heroku[web.1]: Process exited with status 1
2021-07-08T00:29:48.802550+00:00 heroku[web.1]: State changed from starting to crashed
2021-07-08T00:29:48.854986+00:00 heroku[web.1]: State changed from crashed to starting

```

# 参考にさせて頂いたサイト
* https://qiita.com/opengl-8080/items/9dc9243c4e68cd0674f8
* https://qiita.com/yuucu/items/dea6e7a0a655af8fdb91
* https://innovator-japan.hatenablog.com/entry/2018/10/22/131853
* https://teratail.com/questions/140032
* https://help.heroku.com/CTFS2TJK/how-do-i-run-multiple-processes-on-a-dyno