FastAPI 公式のテンプレート "Full Stack FastAPI PostgreSQL" を動かす (2023年05月版)

## 概要

ちょっと用事がありまして FastAPI のプロジェクトが必要でした。
「公式のテンプレートがあるじゃん！」なのですが、メンテが2020年6月で止まっており、 `docker-compose up -d` しても動かなかったので、いろいろ手直ししてひとまず動くところまで持っていきました。

その修正点の備忘録です。(たまってるPRを当てていけば直りそうだけど)

https://github.com/tiangolo/full-stack-fastapi-postgresql

## ということで

### プロジェクトを生成

プロジェクト生成は公式サイトの "how-to-use-it" に従いましょう。

https://github.com/tiangolo/full-stack-fastapi-postgresql#how-to-use-it

で、プロジェクトを生成したら、ひとまずなにも考えずに `docker-compose up -d` でコンテナ群を起動してみましょう。

https://github.com/tiangolo/full-stack-fastapi-postgresql/tree/master/%7B%7Bcookiecutter.project_slug%7D%7D#cookiecutterproject_name

想定通りであればエラーがでるはずです。

### フロントの `fork-ts-checker-webpack-plugin-v5` に関するエラー

フロント側で `fork-ts-checker-webpack-plugin-v5` といったエラーが出た場合です。

<details><summary>エラー詳細</summary>

```text
-  Building for production...
 ERROR  Error: Cannot find module 'fork-ts-checker-webpack-plugin-v5'
Error: Cannot find module 'fork-ts-checker-webpack-plugin-v5'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:582:15)
    at Function.Module._load (internal/modules/cjs/loader.js:508:25)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:22:18)
    at api.chainWebpack.config (/app/node_modules/@vue/cli-plugin-typescript/index.js:106:16)
    at webpackChainFns.forEach.fn (/app/node_modules/@vue/cli-service/lib/Service.js:236:40)
    at Array.forEach (<anonymous>)
    at Service.resolveChainableWebpackConfig (/app/node_modules/@vue/cli-service/lib/Service.js:236:26)
    at PluginAPI.resolveChainableWebpackConfig (/app/node_modules/@vue/cli-service/lib/PluginAPI.js:145:25)
    at module.exports (/app/node_modules/@vue/cli-service/lib/commands/build/resolveAppConfig.js:9:22)
    at build (/app/node_modules/@vue/cli-service/lib/commands/build/index.js:147:50)
    at api.registerCommand (/app/node_modules/@vue/cli-service/lib/commands/build/index.js:89:13)
    at Service.run (/app/node_modules/@vue/cli-service/lib/Service.js:230:12)
    at Object.<anonymous> (/app/node_modules/@vue/cli-service/bin/vue-cli-service.js:36:9)
    at Module._compile (internal/modules/cjs/loader.js:701:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! frontend@0.1.0 build: `vue-cli-service build`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the frontend@0.1.0 build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2023-05-04T04_01_39_533Z-debug.log
ERROR: Service 'frontend' failed to build : The command '/bin/sh -c npm run build' returned a non-zero code: 1
```
</details>

#### 対策

ググると次のようなページがヒットします。

https://github.com/vuejs/vue-cli/issues/6468

Node のバージョンに依存するようです。
Node のバージョンをあげたいのですが公式が v10 までしか対応していませんでした。
ということでネットの海をさまよったところ、フォークして v16 に対応している docker イメージがありましたのでこちらを使わせていただきましょう。

https://github.com/greenaj/node-frontend

ということで修正します。

```diff_docker:[PJDirName]/frontend/Dockerfile
- FROM tiangolo/node-frontend:10 as build-stage
+ FROM greenaj/node-frontend:16 as build-stage
```

なぜか `nginx.conf` のファイル名も変更されているのでそこも修正します。

```diff_docker:[PJDirName]/frontend/Dockerfile
FROM nginx:1.15

COPY --from=build-stage /app/dist/ /usr/share/nginx/html

- COPY --from=build-stage /nginx.conf /etc/nginx/conf.d/default.conf
+ COPY --from=build-stage /default.conf /etc/nginx/conf.d/default.conf
COPY ./nginx-backend-not-found.conf /etc/nginx/extra-conf.d/backend-not-found.conf
```

### poetry に関する 404 Not Found のエラー

バックエンド側で `404: Not Found` といったエラーが出た場合です。

<details><summary>エラー詳細</summary>

```text
Step 3/15 : RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | POETRY_HOME=/opt/poetry python &&     cd /usr/local/bin &&     ln -s /opt/poetry/bin/poetry &&     poetry config virtualenvs.create false
 ---> Running in 374e5a279887
  File "<stdin>", line 1
    404: Not Found
                 ^
SyntaxError: invalid syntax
ERROR: Service 'celeryworker' failed to build : The command '/bin/sh -c curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | POETRY_HOME=/opt/poetry python &&     cd /usr/local/bin &&     ln -s /opt/poetry/bin/poetry &&     poetry config virtualenvs.create false' returned a non-zero code: 1
```
</details>

#### 対策

poetry をインストールしようとしてるんですね。
だけど、インストール先の URL が変わったためエラーとなっているらしいです。
下記の通りにインストール先の URL を書き換えます。

https://python-poetry.org/blog/announcing-poetry-1.2.0/#new-standalone-installer

バックエンドの dockerfile を２つとも書き換えます。

```diff_docker:[PJDirName]/backend/backend.dockerfile
# Install Poetry
- RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | POETRY_HOME=/opt/poetry python && \
+ RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python && \
    cd /usr/local/bin && \
    ln -s /opt/poetry/bin/poetry && \
    poetry config virtualenvs.create false
```

```diff_docker:[PJDirName]/backend/celeryworker.dockerfile
# Install Poetry
- RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | POETRY_HOME=/opt/poetry python && \
+ RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python && \
    cd /usr/local/bin && \
    ln -s /opt/poetry/bin/poetry && \
    poetry config virtualenvs.create false
```

### flower がエラーとなり起動しない

flower がエラーとなり起動しない場合です。

<details><summary>エラー詳細</summary>

```text
Creating full-stack-fastapi-postgresql_db_1       ... done
Creating full-stack-fastapi-postgresql_flower_1       ... error
Creating full-stack-fastapi-postgresql_queue_1    ... done
Creating full-stack-fastapi-postgresql_frontend_1 ... done
Creating full-stack-fastapi-postgresql_proxy_1        ... done
Creating full-stack-fastapi-postgresql_pgadmin_1  ... 
Creating full-stack-fastapi-postgresql_backend_1  ... 
Creating full-stack-fastapi-postgresql_celeryworker_1 ... 

ERROR: for full-stack-fastapi-postgresql_flower_1  Cannot start service flower: Creating full-stack-fastapi-postgresql_pgadmin_1      ... done
Creating full-stack-fastapi-postgresql_backend_1      ... done
Creating full-stack-fastapi-postgresql_celeryworker_1 ... done

ERROR: for flower  Cannot start service flower: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "--broker=amqp://guest@queue:5672//": stat --broker=amqp://guest@queue:5672//: no such file or directory: unknown
ERROR: Encountered errors while bringing up the project.
```
</details>

#### 対策

原因はわかりませんが、新しいバージョンだとダメらしいです。

https://github.com/tiangolo/full-stack-fastapi-postgresql/issues/420

ということで、ひとまず動かしたいのでコメントの記載にある古いバージョンで動かします。

```diff_yaml:[PJDirName]/docker-compose.yml
  flower:
-   image: mher/flower
+   image: mher/flower:0.9.7
    networks:
      - ${TRAEFIK_PUBLIC_NETWORK?Variable not set}
      - default
    env_file:
```

### バックエンドが `AttributeError: module 'h11' has no attribute 'Event'` でエラー

ここまでくると、パット見で動いているように見えますが、実は動いていない場合があります。
`docker-compose ps` で確認すると次のようになっていました。

```text
              Name                             Command               State                 Ports              
--------------------------------------------------------------------------------------------------------------
full-stack-fastapi-                 /start-reload.sh                 Exit 1                                   
postgresql_backend_1                                                                                          
full-stack-fastapi-                 bash /worker-start.sh            Exit 1                                   
postgresql_celeryworker_1                                                                                     
full-stack-fastapi-                 docker-entrypoint.sh postgres    Up       5432/tcp                        
postgresql_db_1                                                                                               
full-stack-fastapi-                 flower --broker=amqp://gue ...   Up       0.0.0.0:5555->5555/tcp,:::5555->
postgresql_flower_1                                                           5555/tcp                        
full-stack-fastapi-                 nginx -g daemon off;             Up       80/tcp                          
postgresql_frontend_1                                                                                         
full-stack-fastapi-                 /entrypoint.sh                   Up       443/tcp, 0.0.0.0:5050->5050/tcp,
postgresql_pgadmin_1                                                          :::5050->5050/tcp, 80/tcp       
full-stack-fastapi-                 /entrypoint.sh --providers ...   Up       0.0.0.0:80->80/tcp,:::80->80/tcp
postgresql_proxy_1                                                            , 0.0.0.0:8090->8080/tcp,:::8090
                                                                              ->8080/tcp                      
full-stack-fastapi-                 docker-entrypoint.sh rabbi ...   Up       15691/tcp, 15692/tcp, 25672/tcp,
postgresql_queue_1                                                            4369/tcp, 5671/tcp, 5672/tcp   
```

バックエンドと celeryworker が Exit 1 で落ちてます。
ということでバックエンドから `docker-compose logs backend` で見ていきましょう。

見てみると `AttributeError: module 'h11' has no attribute 'Event'` となっていました。

<details><summary>エラー詳細</summary>

```text
backend_1       |   File "/usr/local/lib/python3.7/site-packages/httpcore/_sync/__init__.py", line 1, in <module>
backend_1       |     from .connection import HTTPConnection
backend_1       |   File "/usr/local/lib/python3.7/site-packages/httpcore/_sync/connection.py", line 13, in <module>
backend_1       |     from .http11 import HTTP11Connection
backend_1       |   File "/usr/local/lib/python3.7/site-packages/httpcore/_sync/http11.py", line 44, in <module>
backend_1       |     class HTTP11Connection(ConnectionInterface):
backend_1       |   File "/usr/local/lib/python3.7/site-packages/httpcore/_sync/http11.py", line 144, in HTTP11Connection
backend_1       |     self, event: h11.Event, timeout: Optional[float] = None
backend_1       | AttributeError: module 'h11' has no attribute 'Event'
```
</details>

#### 対策

調べてみると httpcore というライブラリのバージョンに依存する問題のようです。

https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/4833

ということで httpcore のバージョンを古いもので固定しましょう。
`pyproject.toml` に追記します。

```diff_toml:[PJDirName]/backend/app/pyproject.toml
[tool.poetry.dependencies]
python = "^3.7"
uvicorn = "^0.11.3"
fastapi = "^0.54.1"
...
python-jose = {extras = ["cryptography"], version = "^3.1.0"}
httpcore = "<=0.15"
importlib-metadata = "<5"
+ httpcore = "<=0.15"
```

書き換えたら `docker-compose build backend` でリビルドしてから up しましょう。

### celeryworker が `AttributeError: 'EntryPoints' object has no attribute 'get'` でエラー

celeryworker が `AttributeError: 'EntryPoints' object has no attribute 'get'` でエラーになる場合があります。

<details><summary>エラー詳細</summary>

```text
celeryworker_1  |   File "/usr/local/lib/python3.7/site-packages/celery/bin/base.py", line 16, in <module>
celeryworker_1  |     from celery import VERSION_BANNER, Celery, maybe_patch_concurrency, signals
celeryworker_1  |   File "/usr/local/lib/python3.7/site-packages/celery/signals.py", line 16, in <module>
celeryworker_1  |     from .utils.dispatch import Signal
celeryworker_1  |   File "/usr/local/lib/python3.7/site-packages/celery/utils/__init__.py", line 19, in <module>
celeryworker_1  |     from .nodenames import nodename, nodesplit, worker_direct
celeryworker_1  |   File "/usr/local/lib/python3.7/site-packages/celery/utils/nodenames.py", line 9, in <module>
celeryworker_1  |     from kombu.entity import Exchange, Queue
celeryworker_1  |   File "/usr/local/lib/python3.7/site-packages/kombu/entity.py", line 9, in <module>
celeryworker_1  |     from .serialization import prepare_accept_content
celeryworker_1  |   File "/usr/local/lib/python3.7/site-packages/kombu/serialization.py", line 456, in <module>
celeryworker_1  |     for ep, args in entrypoints('kombu.serializers'):  # pragma: no cover
celeryworker_1  |   File "/usr/local/lib/python3.7/site-packages/kombu/utils/compat.py", line 93, in entrypoints
celeryworker_1  |     for ep in importlib_metadata.entry_points().get(namespace, [])
celeryworker_1  | AttributeError: 'EntryPoints' object has no attribute 'get'
```
</details>

#### 対策

こちらは importlib-metadata というライブラリのバージョンに依存する問題のようです。

https://stackoverflow.com/questions/73929564/entrypoints-object-has-no-attribute-get-digital-ocean

ということで importlib-metadata のバージョンを古いもので固定しましょう。
`pyproject.toml` に追記します。

```diff_toml:[PJDirName]/backend/app/pyproject.toml
[tool.poetry.dependencies]
python = "^3.7"
uvicorn = "^0.11.3"
fastapi = "^0.54.1"
...
python-jose = {extras = ["cryptography"], version = "^3.1.0"}
httpcore = "<=0.15"
importlib-metadata = "<5"
httpcore = "<=0.15"
+ importlib-metadata = "<5"
```

書き換えたら `docker-compose build celeryworker` でリビルドしてから up しましょう。

## 動いた！

ここまで修正したら、一度リフレッシュしましょう。

```
docker-compose stop
docker-compose build
docker-compose up -d
```

これで `docker-compose ps` を確認すると、すべてのサービスが Up しているはずです。

```text
              Name                             Command               State                 Ports              
--------------------------------------------------------------------------------------------------------------
full-stack-fastapi-                 /start-reload.sh                 Up      80/tcp, 0.0.0.0:8888->8888/tcp,::
postgresql_backend_1                                                         :8888->8888/tcp                  
full-stack-fastapi-                 bash /worker-start.sh            Up                                       
postgresql_celeryworker_1                                                                                     
full-stack-fastapi-                 docker-entrypoint.sh postgres    Up      5432/tcp                         
postgresql_db_1                                                                                               
full-stack-fastapi-                 flower --broker=amqp://gue ...   Up      0.0.0.0:5555->5555/tcp,:::5555->5
postgresql_flower_1                                                          555/tcp                          
full-stack-fastapi-                 nginx -g daemon off;             Up      80/tcp                           
postgresql_frontend_1                                                                                         
full-stack-fastapi-                 /entrypoint.sh                   Up      443/tcp, 0.0.0.0:5050->5050/tcp,:
postgresql_pgadmin_1                                                         ::5050->5050/tcp, 80/tcp         
full-stack-fastapi-                 /entrypoint.sh --providers ...   Up      0.0.0.0:80->80/tcp,:::80->80/tcp,
postgresql_proxy_1                                                           0.0.0.0:8090->8080/tcp,:::8090->8
                                                                             080/tcp                          
full-stack-fastapi-                 docker-entrypoint.sh rabbi ...   Up      15691/tcp, 15692/tcp, 25672/tcp, 
postgresql_queue_1                                                           4369/tcp, 5671/tcp, 5672/tcp   
```

以上です！お疲れさまでした！