Open11

FastAPI+Postgres on Heroku

0Yu0Yu

想定環境:wsl2 ubuntu20.04 LTS

pythonは3.9を使う.
環境にない場合はinstallする

$ sudo apt install -y python3.9 python3.9-venv

以下でパスを確認する.

$ which python3.9
/usr/bin/python3.9
$ python3.9 --version
Python 3.9.5

作業用ディレクトリを作成し、python3.9の仮想環境をactivateする

$ python3.9 -m venv env
$ source ./env/bin/activate

最終的なディレクトリの構造は以下のようになる.

app
   __pycache__
   env
   main.py
   Procfile
   requirements.txt
   .gitigonore
0Yu0Yu

コードの説明は下記のチュートリアルを参照.
https://fastapi.tiangolo.com/ja/advanced/async-sql-databases/

# main.py
from typing import List

import databases
import sqlalchemy
from fastapi import FastAPI
from pydantic import BaseModel

# SQLAlchemy specific code, as with any other app
# ATABASE_URL = "sqlite:///./test.db"
# 今回はHeroku Postgresを利用するため下記をコメントアウト
DATABASE_URL = "postgresql://user:password@postgresserver/db"

database = databases.Database(DATABASE_URL)

metadata = sqlalchemy.MetaData()

notes = sqlalchemy.Table(
    "notes",
    metadata,
    sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True),
    sqlalchemy.Column("text", sqlalchemy.String),
    sqlalchemy.Column("completed", sqlalchemy.Boolean),
)


engine = sqlalchemy.create_engine(
    DATABASE_URL, connect_args={"check_same_thread": False}
)
metadata.create_all(engine)


class NoteIn(BaseModel):
    text: str
    completed: bool


class Note(BaseModel):
    id: int
    text: str
    completed: bool


app = FastAPI()


@app.on_event("startup")
async def startup():
    await database.connect()


@app.on_event("shutdown")
async def shutdown():
    await database.disconnect()


@app.get("/notes/", response_model=List[Note])
async def read_notes():
    query = notes.select()
    return await database.fetch_all(query)


@app.post("/notes/", response_model=Note)
async def create_note(note: NoteIn):
    query = notes.insert().values(text=note.text, completed=note.completed)
    last_record_id = await database.execute(query)
    return {**note.dict(), "id": last_record_id}

今回はPostgresを利用するため、下記のように修正する.

engine = sqlalchemy.create_engine(
    # DATABASE_URL, connect_args={"check_same_thread": False}
    DATABASE_URL
)
0Yu0Yu

Heroku Dashboardで新規アプリケーションを作成し、add-onsを選択する.
postgresqlを検索し選択する.
heroku add-ons

Resources > Heroku Postgres > Settings > View Credentials…
からURIを取得し、main.pyのDATABASE_URLに記述する.
https://dashboard.heroku.com/

0Yu0Yu

Heroku Dashboardで作成したアプリケーションにgitを紐づけしデプロイする.

$ git init
$ heroku git:remote -a app
$ git add .
$ git commit -am "make it better"
$ git push heroku master

https://dashboard.heroku.com/

0Yu0Yu

コンテナデプロイ

  1. コンテナを作る
├── app
│   └── main.py
└── Dockerfile
└── heroku.yml

Dockerfile

FROM python:3.8

WORKDIR /usr/src/api

COPY requirements.txt ./api/

RUN pip install --trusted-host pypi.python.org -r ./api/requirements.txt &&\
  rm -rf ~/.cache/pip

COPY ./api /usr/src/api

CMD gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app

heroku.yml

setup:
  addons:
    - plan: heroku-postgresql
      as: DATABASE
build:
  docker:
    web: Dockerfile
  1. イメージのプッシュ/リリース
heroku container:push web # プロセスタイプをwebに指定
heroku container:release web worker

DBにHeroku Postgresを使う場合、sqlalchemyのcreate_engineで指定するDATABASE_URLはpostgres://をサポートしていないためpostgresql://に書き換える必要がある(HerokuPostgresのconfigureからコピペするとpostgres://のままになっているため注意)

参考

https://fastapi.tiangolo.com/ja/deployment/docker/
https://devcenter.heroku.com/articles/build-docker-images-heroku-yml#getting-started
https://devcenter.heroku.com/ja/articles/container-registry-and-runtime
https://tkstock.site/2021/12/03/heroku-sqlalchmy-create_engine-エラー-nosuchmoduleerror/
https://dev.to/ejach/how-to-deploy-a-python-flask-app-on-heroku-using-docker-mpc
https://docs.docker.jp/engine/reference/builder.html