fly.ioにPython(Flask) WebAppをデプロイ→git pushで自動デプロイ

# はじめに
自作Web Appをherokuで運用しようとしていたのですが、herokuの料金プランが変更されるとのことなので急遽fly.ioを試してみました。
fly.ioの公式サイトのマニュアルを参考に作業した手順を自分用に簡単にまとめます。

# 作業手順
1. [公式サイト](https://fly.io/)からGitHubアカウントでサインイン
1. [こちら](https://fly.io/docs/languages-and-frameworks/python/)を参考に、Pythonのサンプルプロジェクトをcloneしてデプロイする
flyctlのインストールは[こちら](https://fly.io/docs/hands-on/install-flyctl/)を参考
1. [こちら](https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/)を参考に、以下を実施
  3-1. 自動デプロイしたいGitHubのリポジトリを表示し、設定を開く
  3-2. Secrets → Actionsから新しいシークレットを作成する
　　名前：FLY_API_TOKEN
　　トークン：`flyctl auth token`で取得した文字列
  3-3. .gitignoreに`fly.toml`がある場合、削除しておく
  3-4. ./.github/workflows/main.ymlファイルを作成し、内容をコピペして保存する
  3-5. 変更をcommitし、pushすると自動デプロイされる

※マニュアルのステップ1, 5は不要(既にサンプルプロジェクトをcloneしており、かつ本記事用に新しいリポジトリを作成したため)
　代わりに以下を実施
```
公式のサンプルプロジェクトをclone → .gitディレクトリ削除 → git init → git remote add <自分のリポジトリ> → git push origin master
```
※マニュアルのステップ7は不要(flyctl launch実行時にfly.tomlファイルが作成されるため)

# DB追加
今回はpsycopg2を使ってDBに接続することにしました。

1. hellofly.pyを以下に変更
※本来は接続情報ベタ書きは良くないですが、今回はお試しなのでベタ書きしました
```python
from flask import Flask, render_template
import psycopg2

app = Flask(__name__)

connector = psycopg2.connect('postgresql://{user}:{passwd}@{host}:{port}/{dbname}'.format(
  user="<launchした際に表示された情報の「Username」>",
  passwd="<launchした際に表示された情報の「Password」>",
  host="<launchした際に表示された情報の「Hostname」>",
  port="<launchした際に表示された情報の「Proxy Port」>",
  dbname="postgres"
))

@app.route('/')
def hello():
    cursor = connector.cursor()
    cursor.execute("SELECT version();")
    version = cursor.fetchone()
    return render_template('hello.html', version=version)
```
2．htmlを以下に変更
```html
<!DOCTYPE html>
<html lang="jp">
  <head>
  </head>
  <body>
    <h1>Hello from Fly</h1>
    <p>{{version}}</p>
  </body>
</html>
```
3．requirements.txtを以下に変更
```
click==8.1.3
Flask==2.2.2
gunicorn==20.1.0
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.1
Werkzeug==2.2.2
psycopg2-binary==2.9.3　←追加
```
ここまでで以下画面が表示できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177673/83b8e9b0-13e7-6167-2768-3cc59d19b482.png)

### ハマったポイント
#### psycopg2をインストールしようとするとデプロイ時にエラーになった
GitHub Actionsでエラーになっていた
<details><summary>エラー</summary>


```
Collecting psycopg2==2.9.3
  Downloading psycopg2-2.9.3.tar.gz (380 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 380.6/380.6 kB 43.5 MB/s eta 0:00:00
  Preparing metadata (setup.py): started
  Preparing metadata (setup.py): finished with status 'error'
  error: subprocess-exited-with-error
  
  × python setup.py egg_info did not run successfully.
  │ exit code: 1
  ╰─> [25 lines of output]
      /layers/paketo-buildpacks_cpython/cpython/lib/python3.10/site-packages/setuptools/config/setupcfg.py:508: SetuptoolsDeprecationWarning: The license_file parameter is deprecated, use license_files instead.
        warnings.warn(msg, warning_class)
      running egg_info
      creating /tmp/pip-pip-egg-info-fgdgnfc0/psycopg2.egg-info
      writing /tmp/pip-pip-egg-info-fgdgnfc0/psycopg2.egg-info/PKG-INFO
      writing dependency_links to /tmp/pip-pip-egg-info-fgdgnfc0/psycopg2.egg-info/dependency_links.txt
      writing top-level names to /tmp/pip-pip-egg-info-fgdgnfc0/psycopg2.egg-info/top_level.txt
      writing manifest file '/tmp/pip-pip-egg-info-fgdgnfc0/psycopg2.egg-info/SOURCES.txt'
      
      Error: pg_config executable not found.
      
      pg_config is required to build psycopg2 from source.  Please add the directory
      containing pg_config to the $PATH or specify the full executable path with the
      option:
      
          python setup.py build_ext --pg-config /path/to/pg_config build ...
      
      or with the pg_config option in 'setup.cfg'.
      
      If you prefer to avoid building psycopg2 from source, please install the PyPI
      'psycopg2-binary' package instead.
      
      For further information please check the 'doc/src/install.rst' file (also at
      <https://www.psycopg.org/docs/install.html>).
      
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed

× Encountered error while generating package metadata.
╰─> See above for output.

note: This is an issue with the package mentioned above, not pip.
hint: See above for details.

error: exit status 1
ERROR: failed to build: exit status 1
Error failed to fetch an image or build from source: executing lifecycle: failed with status code: 51


Error: Process completed with exit code 1.
```
</details>

→psycopg2-binaryを使うことで回避できた
また、Dockerfileからのデプロイならpsycopg2でも可能らしい(試していない)
https://community.fly.io/t/cant-install-psycopg2-due-to-missing-pg-config/7382/3

#### connectorの`dbname`に何を設定すればいいのかわからない
→flyctlからpostgresに入ってDB名を確認した
```
flyctl postgres connect -a <db名>
\l
   Name    |   Owner    | Encoding |  Collate   |   Ctype    |     Access privileges
-----------+------------+----------+------------+------------+-------------------------------------------
 postgres  | flypgadmin | UTF8     | en_US.utf8 | en_US.utf8 |
 template0 | flypgadmin | UTF8     | en_US.utf8 | en_US.utf8 | =c/flypgadmin + flypgadmin=CTc/flypgadmin
 template1 | flypgadmin | UTF8     | en_US.utf8 | en_US.utf8 | =c/flypgadmin + flypgadmin=CTc/flypgadmin
(3 rows)
\q
```

### CRUD(と言いつつCreateだけ)
1．hellofly.pyを以下に変更
```python
from flask import Flask, render_template
import psycopg2

app = Flask(__name__)

connector = psycopg2.connect('postgresql://{user}:{passwd}@{host}:{port}/{dbname}'.format(
  user="<launchした際に表示された情報の「Username」>",
  passwd="<launchした際に表示された情報の「Password」>",
  host="<launchした際に表示された情報の「Hostname」>",
  port="<launchした際に表示された情報の「Proxy Port」>",
  dbname="postgres"
))

@app.route('/')
def hello():
    cursor = connector.cursor()
    cursor.execute("SELECT version();")
    version = cursor.fetchone()

    cursor.execute("SELECT * FROM test;")
    select = cursor.fetchall()
    return render_template('hello.html', version=version, select=select)

@app.route('/insert')
def insert():
    cursor = connector.cursor()
    cursor.execute("INSERT INTO test(name) VALUES ('hoge');")
    connector.commit()
    return redirect('./')
```
2．htmlを以下に変更
```html
<!DOCTYPE html>
<html lang="jp">
  <head>
  </head>
  <body>
    <h1>Hello from Fly</h1>
    <p>{{version}}</p>
    <br />
    <p>{{select}}</p>
    <a href="./insert">insert</a>
  </body>
</html>
```

「insert」リンクをクリックするとレコードが追加される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177673/23293d32-a1dd-3630-ad97-d452f8a310f9.png)
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177673/8159aef4-e59b-c3dc-188e-546b095233f3.png)

insertができれば同じ要領でupdate, deleteも可能です。

# おわりに
本記事ではここまで（fly.ioに環境構築 + デプロイ + postgreDB使用）とします。
これで自作WebAppを移行する準備ができました。
(まだ開発終わってないですが...)

開発終わってデプロイしたらまた記事を書きたいと思います。