2022年10月9日日曜日

fly.io に Python Flask アプリをデプロイ

はじめに

PaaS である fly.io で Python Flaskアプリ (DB は Postgres を利用) をデプロイしてみたのでまとめています。

なお、「fly.io に Python Flask アプリをデプロイするためのローカルの作業及び開発環境を作成」にて作成した Docker コンテナ内で作業します。

fly.io サイトでアカウント登録

https://fly.io/ でアカウントを登録しておきます。

作業用の Docker コンテナを起動

fly.io に Python Flask アプリをデプロイするためのローカルの作業及び開発環境を作成」にて作成した Docker コンテナを起動

$ docker start --interactive fly-work

woker ユーザーに su して作業ディレクトリに移動

# su - worker
$ cd ~/work/

この状態で以下の 2つのファイルが存在します。

$ tree -a ./
./
├── requirements.txt
└── testapp.py

0 directories, 2 files

flyctl をインストール

fly.io のコマンドラインツール flyctl をインストール

$ curl -L https://fly.io/install.sh | sh

環境変数を .bashrc に記載

$ echo 'export FLYCTL_INSTALL="/home/worker/.fly"' >> ~/.bashrc
$ echo 'export PATH="$FLYCTL_INSTALL/bin:$PATH"' >> ~/.bashrc

環境変数を反映

$ source ~/.bashrc

fly.io にログイン

$ flyctl auth login
  • GUI 環境が無いので以下のメッセージが出ます。

    failed opening browser. Copy the url (https://fly.io/app/auth/cli/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) into a browser and continue
    Opening https://fly.io/app/auth/cli/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
    

ブラウザでメッセージ中の URL https://fly.io/app/auth/cli/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx を開き、未ログインの場合はログインし、「Continue as [登録したメールアドレス]」をクリック

作業環境で以下のメッセージとなり、flyctl でのログイン完了

successfully logged in as xxxxxxxx@xxxx.xxx

Python Flask アプリをローンチ

flyctl コマンドでローンチを実施します。しかし、ローカルに Docker イメージを作成する環境が無いためか、この段階ではデプロイまでは実施されません。

Fly アプリをローンチ

$ flyctl launch
  • アプリ名を聞いてくるので入力

    ? App Name (leave blank to use an auto-generated name): myflyapp
    
  • リージョンを聞いてくるので Tokyo を選択してみました

    ? Select region:  [Use arrows to move, type to filter]
    ... 省略 ...
    > nrt (Tokyo, Japan)
    
  • Postgres DB をセットアップするか聞いてきますが、別で作成しているので、ここではセットアップしません

    ? Would you like to set up a Postgresql database now? No
    
  • 最後のこのメッセージが表示され、作成された Procfile を実際にあわせて編集する必要があるとのことです

    We have generated a simple Procfile for you. Modify it to fit your needs and run "fly deploy" to deploy your application.
    

二つのアプリが設定されました

$ flyctl apps list
NAME                OWNER           STATUS          PLATFORM        LATEST DEPLOY
fly-builder-XXXX    personal        suspended       machines
myflyapp            personal        pending
  • myflyapp

    • flyctl launch で設定したアプリ
  • fly-builder-XXXX

    • デプロイ時に Docker Image を作成するためのリモートビルダーアプリ
    • ローカルで Docker イメージを作成して Fly サイトへ push するのがデフォルトの動作のようですが、Docker デーモンがローカルに無い場合はローカルで Docker イメージを作成できないので Fly サイトにリモートビルダーアプリが作成され、これが Docker イメージを作成するとのことです。

また、Procfile と fly.toml が作成されました。

$ tree -a
.
├── Procfile
├── fly.toml
├── requirements.txt
└── testapp.py

0 directories, 4 files

fly.toml は fly アプリの設定ファイルとのことです。

Procfile の中身を確認

$ cat Procfile
# Modify this Procfile to fit your needs
web: gunicorn server:app

Procfile を以下のように書き換え

$ cat Procfile
# Modify this Procfile to fit your needs
web: gunicorn testapp:app --log-file=-
  • server:app の部分を実際の Python スクリプトに合わせて testapp:app に書き換え

  • testapp.py スクリプトの中の Flask オブジェクトが app = Flask(__name__) となっているので testapp:app と指定します

  • ログを取得できるように --log-file=- を追加

WSGI サーバーとして gunicorn を使用するので requirements.txt に追加

$ cat requirements.txt
gunicorn
Flask
Flask-SQLAlchemy
psycopg2-binary

Python Flask アプリが使用する Postgres DB 用の fly アプリを作成

Postgres DB 用の fly アプリを作成

$ flyctl postgres create
  • アプリ名を聞いてくるので入力

    ? Choose an app name (leave blank to generate one): mydbapp
    
  • リージョンを聞いてくるので Tokyo を選択してみました

    ? Select regions:  [Use arrows to move, type to filter]
    ... 省略 ...
    > Tokyo, Japan (nrt)
    
  • 設定を聞いてくるので以下を選択してみました。(これ以外の選択だと有料プランが必要になるかも)

    ? Select configuration:  [Use arrows to move, type to filter]
    > Development - Single node, 1x shared CPU, 256MB RAM, 1GB disk
    
  • DB への接続情報が表示されました。後で参照できるよう、どこかセキュアな場所に保存しておきます。(二度と確認することができないとのことです)

    Postgres cluster mydbapp created
      Username:    postgres
      Password:    XXXXXXXXXXXXXXXXXXXXXXX
      Hostname:    mydbapp.internal
      Proxy Port:  5432
      Postgres Port: 5433
    

Postgres DB アプリ mydbapp が作成され起動していることを確認

$ flyctl apps list
NAME                OWNER           STATUS          PLATFORM        LATEST DEPLOY
fly-builder-XXXX    personal        suspended       machines
mydbapp             personal        running         nomad           2m49s ago
myflyapp            personal        pending

flyctl を使って Postgres DB に接続

$ flyctl postgres connect --app mydbapp
  • 通常の psql ツールで接続されているようです。

  • ユーザー名は指定していませんが、postgres ユーザーで接続されました。パスワードは不要でした。

  • データベースを確認

    postgres=# \l
                                        List of databases
      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)
    
    • 通常の以下のデータベースのみ作成されていました。
      • postgres
      • template0
      • template1
  • 接続先のデータベース名も postgres データベースでした。

    postgres=# select current_database();
    current_database
    ------------------
    postgres
    (1 row)
    
  • 終了

    postgres=# \q
    

ローカルの psql ツールやローカルの Python スクリプトから Postgres DB に接続するには flyctl proxy を使うとのことです。

  • flyctl proxy 起動

    $ flyctl proxy 15432:5432 --app mydbapp
    Proxying local port 15432 to remote [mydbapp.internal]:5432
    
    • ローカルでリッスンする proxy ポートは 15432
    • リモートの Postgres DB ポートは 5432
  • 例えば、ローカルの psql ツールで接続する場合

    $ psql postgres://postgres:XXXXXXXXXXXXXXXXXXXXXXX@localhost:15432/
    
    • パスワード XXXXXXXXXXXXXXXXXXXXXXX は上記で控えたものを使用
  • psql 終了

    postgres=# \q
    
  • flyctl proxy も Ctrl-C で終了

Python Flask アプリと Postgres DB アプリを紐づけてテーブル作成とデータ挿入

Python Flask アプリ myflyapp が Postgres DB アプリ mydbapp に接続できるよう、両者を紐づけ

$ flyctl postgres attach \
  --app myflyapp \
  mydbapp

Postgres cluster mydbapp is now attached to myflyapp
The following secret was added to myflyapp:
  DATABASE_URL=postgres://myflyapp:XXXXXXXX@top2.nearest.of.mydbapp.internal:5432/myflyapp
  • myflyapp と mydbapp が紐づけられました
  • myflyapp に上記の環境変数 DATABASE_URL が設定されました
  • mydbapp に以下が作成されています
    • myflyapp データベース
    • myflyapp ユーザー (パスワード XXXXXXXX)

ローカルの psql ツールで mydbapp に接続するための flyctl proxy 起動

$ flyctl proxy 15432:5432 --app mydbapp

Python Flask アプリ testapp.py では Flask-SQLAlchemy を使用しているため、テーブル作成やデータ挿入等は Python を使って testapp.py で作成した SQLAlchemy オブジェクトを使用します。

Python 起動

$ DATABASE_URL=postgres://myflyapp:XXXXXXXX@localhost:15432/myflyapp \
python3
  • 後に testapp.py をモジュールとしてインポートしますが、環境変数として DATABASE_URL を参照するので設定して Python を起動してます。

  • testapp.py をモジュールとしてインポート

    >>> import testapp
    
  • テーブル作成

    >>> testapp.db.create_all()
    
  • 作成された test_record テーブルにデータ挿入

    >>> record = testapp.TestRecord()
    >>> record.test_col = 'Hello World'
    
    >>> testapp.db.session.add(record)
    >>> testapp.db.session.commit()
    
  • Python 終了

    >>> exit()
    

testdb データベースが作成されていることを確認

$ psql \
postgres://myflyapp:XXXXXXXX@localhost:15432/myflyapp \
--command '\l'

   Name    |   Owner    | Encoding |  Collate   |   Ctype    |     Access privileges
-----------+------------+----------+------------+------------+---------------------------
 myflyapp  | flypgadmin | UTF8     | en_US.utf8 | en_US.utf8 |
 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
(4 rows)

testapp.py の TestRecord オブジェクトに対応する test_record テーブルが testdb データベースに作成されていることを確認

$ psql \
postgres://myflyapp:XXXXXXXX@localhost:15432/myflyapp \
--command '\d'

            List of relations
 Schema |    Name     | Type  |  Owner
--------+-------------+-------+----------
 public | test_record | table | myflyapp
(1 row)

test_record テーブルにデータが挿入されていることを確認

$ psql \
postgres://myflyapp:XXXXXXXX@localhost:15432/myflyapp \
--command 'select * from test_record'

  test_col
-------------
 Hello World
(1 row)
  • flyctl proxy を Ctrl-C で終了

Python Flask アプリをデプロイ

Python Flask アプリ myflyapp をデプロイ

$ flyctl deploy

割り当てられた IP を確認

$ fly ips list
VERSION IP                      TYPE    REGION  CREATED AT
v4      XXX.XXX.XXX.XXX         public  global  5m19s ago
v6      XXXX:XXXX:X::XXXX       public  global  5m16s ago

myflyapp アプリのステータスを確認

$ flyctl status
App
  Name     = myflyapp
  Owner    = personal
  Version  = 0
  Status   = running
  Hostname = myflyapp.fly.dev
  Platform = nomad

Deployment Status
  ID          = XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
  Version     = v0
  Status      = successful
  Description = Deployment completed successfully
  Instances   = 1 desired, 1 placed, 1 healthy, 0 unhealthy

Instances
ID              PROCESS VERSION REGION  DESIRED STATUS  HEALTH CHECKS           RESTARTS        CREATED
XXXXXXXX        app     0       nrt     run     running 1 total, 1 passing      0               1m31s ago
  • 特に、Hostname = myflyapp.fly.dev より、外部のブラウザから Python Flask アプリ myflyapp にアクセスするための URL が http://myflyapp.fly.dev/ であることが確認できる

  • また、flyctl open を実行すると作業環境は GUI が無いのでブラウザの起動に失敗しますが、表示されるメッセージからもアクセスする URL を確認することができる

    $ flyctl open
    opening http://myflyapp.fly.dev ...
    Error failed opening http://myflyapp.fly.dev: exec: "xdg-open": executable file not found in $PATH
    

ブラウザで http://myflyapp.fly.dev/ でアクセス

  • myflyapp アプリが mydbapp から取得した Hello World が表示されれば正常に動作しています。

参考

0 件のコメント:

コメントを投稿