当サイトは、アフィリエイト広告を利用しています

【Docker × FastAPI 】コンテナのFastAPIアプリをローカルからリモートデバッグする方法

作成日:2024月12月11日
更新日:2024年12月16日

dockerコンテナ内のuvicornサーバーで起動しているFastAPIアプリケーションを
ローカルのVSCodeからリモートデバッグする方法を調べたので忘備録として残す。

当記事では

  • FastAPIアプリケーションのデバッグ方法
  • リモートデバッグの利点
  • リモートデバッグの方法
  • リモートデバッグの実行

についてまとめる

FastAPIアプリケーションのデバッグ方法

VSCode上でFastAPIアプリケーションをdockerコンテナを使って
開発している場合、デバッグ方法としては

  • コンテナに入って直接デバッグをする
  • ローカルからコンテナのアプリに対してリモートでデバッグする

がある。

コンテナに入って直接デバッグをする

こちらの方法はVSCodeからコンテナに接続しコンテナ上でデバッグを行う。
コンテナ上でUvicornをデバッグモードで起動してデバッグするイメージ。
※コンテナに接続後に手動でデバッグモードでUvicornを起動する必要がある。

下記のようなイメージ 2024-12-09-18-12-27

  • コンテナに接続する
  • コンテナ内でデバッガを起動してデバッグ

方法については下記記事で紹介しているのでご参照ください

ローカルからコンテナのアプリに対してリモートでデバッグする

こちらの方法はコンテナで起動しているUvicorn上のFastAPIアプリに対して
ローカルからリモートでアタッチしてデバッグする。
下記のようなイメージ 2024-12-09-18-08-04

  • ローカルからコンテナ上のuvicornサーバーにアタッチ
  • ローカルのソースとコンテナのソースはバインドマウントで紐づけ
  • デバッグはローカルで起動
  • ローカルでコンテナ上のuvicorn上で動くアプリケーションのソースと紐づいたソースをデバッグ

今回はこっちの方法を解説する。

リモートデバッグのメリット

リモートデバッグのメリットとしては

「コンテナ用VSCodeで開く必要がない」

というのがある。

コンテナに接続してコンテナ上でデバッグする場合、コンテナ用のVSCodeを開く
必要があるが

  • 割と重いのでパソコンによっては全体の処理が遅くなる
  • コンテナ用VSCodeを開くまでに時間がかかる
  • コンテナ用VSCodeに別途、拡張機能のインストールが必要

と、意外とめんどくさいし時間がかかる。

リモートデバッグでローカルのVSCodeからリモートで
デバッグするので無駄な時間が省ける。

リモートデバッグの方法

サンプルのFastAPIのREST APIをコンテナ上のuvicornで実行し
デバッグする手順をまとめる。

リモートデバッグは「debugpy」というパッケージを使って行う

debugpyとは?

Pythonアプリケーションのリモートデバッグを可能にするパッケージで
VSCodeのデバッガクライアントと連携して、Pythonプログラムのデバッグをサポートする。
このパッケージを使って、リモート環境で実行されているコンテナのアプリケーションに対して
デバッグする。

debugpyを使ったデバッグの流れ

全体の動作としては下記のようなイメージ。

  1. コンテナが起動すると、「python -m debugpy」でリモートデバッグサーバーが立ち上がる
  2. debugpy はポート5678でクライアント接続を待機。
  3. デバッグが可能な状態で、uvicornサーバーがアプリケーションをポート8000で起動。
  4. VSCodeのデバッガを使って、5678にアタッチすると、アプリケーションの動作をリアルタイムでデバッグできる

debugpyのためのポート開放

上記のような仕組みのためリモートデバッグはデバッグする対象にdebugpyが接続するため はポートを開けてやる必要がある。

その方法としては

  • サーバー起動時にコマンドでポートを空ける
  • ソース自体にポート解放を仕込む

の2パターンがある

サーバー起動時にコマンドでポートを空ける方法

まずはサーバー起動時にdebugpyが接続するためのポートを空ける方法をまとめる。

この方法のメリットは

  • 本体のソースにデバッグ用のソースを仕込む必要がない
  • 比較的簡単にできる

になる。

逆にデメリットとしては

  • uvicorn起動部分(すなわちアプリケーションのエントリポイント以前のコード)をデバッグするができない

がある。
※詳しくはdockerfileに記載。

環境

下記の環境で行う

  • Windows10
  • Docker version 24.0.2(Docker for Windows)
  • VScode
  • Remote Development(VScodeの拡張機能)
  • fastapi 0.115.6
  • python 3.12

構成

全体的なプロジェクト構成は下記のようにする

bash
.
|-- .vscode
| `-- launch.json
|-- app
| |-- __init__.py
| |-- asgi.py
| |-- endpoints.py
| `-- main.py
|-- docker-compose.yml
|-- dockerfile
`-- requirements.txt

動かすのに最低限の構成。
各ファイルを解説する

requirements.txt

requirements.txt
fastapi==0.115.6
uvicorn[standard]==0.32.1
debugpy==1.8.9

必要なパッケージをインストールする

dockerfile

Dockerfile
FROM python:3.12
# PYTHONPATHの設定
ENV PYTHONPATH=/workspace/app
# workspaceディレクトリ作成、移動
WORKDIR /workspace
# プロジェクトディレクトリにコピー
COPY requirements.txt /workspace
# 必要パッケージのインストール
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
# debugpyを使ってuvcornサーバーを起動
CMD ["python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "-m", "uvicorn", "asgi:app", "--reload", "--host", "0.0.0.0", "--port", "8000"]

必要な依存関係をインストールしたDockerイメージを作成する

一番重要なのが「CMD」の部分なので細かく解説する
CMDに指定したコマンドがコンテナ起動時に実行される

python -m debugpy --listen 0.0.0.0:5678の部分

debugpyによるリモートデバッグのためのサーバー(ポート 5678)を起動する。
このコマンドに続けて実行したいモジュールやスクリプトを指定することで、デバッグ対象を起動できる

-m uvicorn asgi:app --reload --host 0.0.0.0 --port 8000の部分

debugpyがラップする対象として uvicornモジュールを指定しているので
debugpyの機能が有効な状態でuvicornが起動する

  • 「asgi:app」はFastAPIのアプリケーションインスタンスまでのパスを指定している。
  • 「--reload」はソースが変更された場合にuvicornを再起動する※いわゆるホットリロード
  • 「--host 0.0.0.0」はホストからもアクセス可能にするため0.0.0.0(すべてのIPアドレス)に指定
  • 「--port 8000」はサーバーが使用するポート番号を指定※docker-composeのコンテナ側のポートと合わせる

uvicorn起動部分をデバッグできないデメリットについて

debugpy を -m フラグでコマンドとして使用すると、指定されたモジュール(ここでは uvicorn)の実行をそのままラップする。
この場合、デバッガがアタッチできるのはラップされたモジュールの中に限られるためuvicorn自体の
起動部分やその前の処理にはアクセスできないためデバッグ不可となる。

具体的にはアプリケーションの起動部分である

  • app/main.py
  • app/asgi.py

のデバッグができない。

docker-compose.yml

docker-compose.yml
version: "3"
services:
fastapi:
container_name: "fastapi"
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/workspace
tty: true
ports:
- 8000:8000 # ホストマシンのポート8000を、docker内のポート8000に接続する
- 5678:5678 # debugpリモートデバッグ用のポート
  • dockerfileからdockerイメージを作成してコンテナを作成する
  • ホストマシンのポート8000を、docker内のポート8000に接続する
  • リモートデバッグのためのサーバー(ポート 5678)も設定する※これがないとデバッグできない

app/endpoints.py

エンドポイントを管理するモジュール

endpoints.py
from fastapi import APIRouter
from fastapi.responses import JSONResponse
router = APIRouter()
# ディクショナリリスト
users = [
{"user_id": "1", "name": "Tujimura", "age": 11},
{"user_id": "2", "name": "mori", "age": 20},
{"user_id": "3", "name": "shimada", "age": 50},
{"user_id": "4", "name": "kyogoku", "age": 70}
]
# ディクショナリの一覧を取得
@router.get("/")
async def get_users():
return JSONResponse(status_code=200, content=users)

GETでディクショナリリストを返却する

app/main.py

アプリケーションの初期化処理を行うモジュール。

main.py
from fastapi import FastAPI
import endpoints as endpoints
def create_app():
# アプリケーションインスタンスの作成
app = FastAPI()
# ルーティング設定
app.include_router(endpoints.router)
return app

FastApiのアプリケーションインスタンスを生成して
エンドポイントを設定後、返却する関数を定義する。

app/asgi.py

FastApiアプリケーションをASGIサーバーで起動するための エントリーポイントとなるモジュール

asgi.py
from main import create_app
app = create_app()

Uvicorn起動時にこのFastApiアプリケーションインスタンスまでのパスを指定して 起動することでUvicornでFaskApiアプリケーションを動かすことができる。

dockerfileのCMDの「app.asgi:app」はこのモジュールのappを指定している

.vscode/launch.json

VScodeのデバッガの設定ファイル

launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "FastAPI Container Debug",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}", //コンテナのルートとバインドさせているローカルのフォルダ
"remoteRoot": "/workspace" //コンテナのルートフォルダ
}
]
}
]
}

pathMappingsでコンテナのソースとローカルのソースを紐づけている

リモートデバッグではコンテナ内のソースをローカルからデバッグするため コンテナ内のソースとローカルのソースを紐づけておく必要がある。

ここがずれるとデバッグのブレークポイントが機能しないので注意。

docker-compose.ymlで

docker-compose.yml
volumes:
- .:/workspace

のようにVSCodeで開いているルートフォルダとコンテナの「/workspace」を
バインドマウントしているので、それに合わせている。

リモートデバッグ実行

設定は完了したのでVSCodeからリモートデバッグをしてみる

コンテナを起動する

ターミナルからコマンドをたたいてコンテナを起動する

コンテナ起動
docker-compose up -d
  • docker-compose.ymlがあるディレクトリで実行する

起動確認

コンテナが起動していることを確認する

起動確認
docker ps

起動している場合は起動中のコンテナが表示される

Uvicornの起動確認

Uvicornの起動確認
$ docker logs <コンテナのID>
0.01s - Debugger warning: It seems that frozen modules are being used, which may
0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off
0.00s - to python to disable frozen modules.
0.00s - Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation.
INFO: Will watch for changes in these directories: ['/workspace']
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: Started reloader process [1] using WatchFiles
0.01s - Debugger warning: It seems that frozen modules are being used, which may
0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off
0.00s - to python to disable frozen modules.
0.00s - Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation.
0.01s - Debugger warning: It seems that frozen modules are being used, which may
0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off
0.00s - to python to disable frozen modules.
0.00s - Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation.
INFO: Started server process [20]
INFO: Waiting for application startup.
INFO: Application startup complete.

「docker ps」コマンドで調べたIDを使ってコンテナのログを確認する

VSCodeのデバッグを起動する

VSCodeのデバッグを起動して、コンテナのUvicornで動作する
FastAPIアプリに対して、ローカルからリモートでデバッグをする

デバッグ起動

デバッグタブでlaunch.jsonで設定したデバッガを指定して起動する 2024-12-09-14-42-11 ※F5でも起動できる

うまく起動できると 2024-12-09-14-44-21 のようになる

また
デバッグのログは下記で確認できる
2024-12-09-14-47-44

ブレークポイント設定

任意の場所にブレークポイントを設定し
REST APIにリクエストすると止まるのを確認

リクエスト

bash
$ curl -X GET http://localhost:8000

ブレークポイント

2024-12-09-14-51-13

ホットリロードについて

dockerfileでCMDでUvicornにreloadオプションをつけて起動しているので
ソースを変更した場合は、再起動され、変更が反映される。

ソースを変更した際のdockerのlogを確認すると

bash
WARNING: WatchFiles detected changes in 'app/endpoints.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [20]
0.03s - Debugger warning: It seems that frozen modules are being used, which may
0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off
0.00s - to python to disable frozen modules.
0.00s - Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation.
INFO: Started server process [49]
INFO: Waiting for application startup.
INFO: Application startup complete.

のように再起動されている。
※デバッガー起動中であっても可能

ソース自体にポート解放を仕込む方法

こちらはソース自体にポートを開けるコードを仕込む。

この方法のメリットは

  • uvicorn起動部分のデバッグ可能

になる。

逆にデメリットとしては

  • 本体ソースに変更を入れる必要がある
  • サーバーが起動時のポート解放に比べると少し複雑

がある。

ベースは「サーバー起動時にコマンドでポートを空ける方法」のコードを使うので
変更点のみまとめる

新規追加ファイルは

  • .env
  • app/config.py

変更ファイルは

  • dockerfile
  • docker-compose.yml
  • app/main.py

になる。

.env

.env
EABLE_DEBUGPY=true # デバッグモード設定
DEFAULT_DEBUG=true # 初期デバッグ設定

環境変数を二つ設定する

EABLE_DEBUGPY

デバッグモードでアプリケーションを起動するかの設定
「true」の場合はデバッグモードで起動する

DEFAULT_DEBUG

起動初期からデバッグするかの設定。
これをtrueにした場合は、デバッグが起動するまでアプリケーションの起動を
待機する。

つまりこれをtrueにした場合、uvicorn起動部分のデバッグ可能になる

app/config.py

config.py
import os
class Config:
# デバッグモード
EABLE_DEBUGPY = os.environ.get('EABLE_DEBUGPY')
# 初期デバッグ
DEFAULT_DEBUG = os.environ.get('DEFAULT_DEBUG')
config = Config() # グローバルインスタンス

環境変数をプログラム内でグローバルインスタンスとして保持させ
他のプログラムからはConfigを通じて参照するようにする

dockerfile

dockerfile
FROM python:3.12
# PYTHONPATHの設定
ENV PYTHONPATH=/workspace/app
# workspaceディレクトリ作成、移動
WORKDIR /workspace
# プロジェクトディレクトリにコピー
COPY requirements.txt /workspace
# 必要パッケージのインストール
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
# uvcornサーバーを起動(debugpyを使わない)
CMD ["uvicorn", "asgi:app", "--reload", "--host", "0.0.0.0", "--port", "8000"]

uvicornをdebugpyを使わずに起動する。
※debugpyのためのポート開放はコードでするため。

docker-compose.yml

docker-compose.yml
version: "3"
services:
fastapi:
container_name: "fastapi"
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/workspace
tty: true
env_file:
- .env
ports:
- 8000:8000 # ホストマシンのポート8000を、docker内のポート8000に接続する
- 5678:5678 # debugpyリモートデバッグ用のポート

コンテナ起動時にコンテナに環境変数を.envファイルから設定する

app/main.py

main.py
from fastapi import FastAPI
import endpoints as endpoints
from config import Config
def create_app():
# デバッグモード判定
if Config.EABLE_DEBUGPY == "true":
import debugpy
# デバッグサーバー起動
debugpy_port = 5678
debugpy.listen(("0.0.0.0", debugpy_port))
print(f"[DEBUG] Debugpy server is listening on 0.0.0.0:{debugpy_port}")
# 初期デバッグ判定
if Config.DEFAULT_DEBUG == "true":
print("[DEBUG] Waiting for debugger to attach...")
debugpy.wait_for_client()
print("[DEBUG] Debugger attached. Continuing execution...")
# アプリケーションインスタンスの作成
app = FastAPI()
# ルーティング設定
app.include_router(endpoints.router)
return app

アプリケーションの起動時に環境変数をConfigから取得し
その値によって

  • デバッグモードか?
  • 初期デバッグか?

を判定している。

リモートデバッグ実行

リモートデバッグを実行する。

EABLE_DEBUGPY=true、DEFAULT_DEBUG=trueの場合

デバッグモードで起動し、初期デバッグも行う。

ブレークポイント設定

アプリケーションの起動部分でブレークポイントを設定 2024-12-09-17-15-53 ※debugpyが警告になっているのはローカルではインストールしていないため

コンテナ起動

コンテナを起動後のコンテナのログは

コンテナログ
INFO: Will watch for changes in these directories: ['/workspace']
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: Started reloader process [1] using WatchFiles
0.00s - Debugger warning: It seems that frozen modules are being used, which may
0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off
0.00s - to python to disable frozen modules.
0.00s - Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation.
[DEBUG] Debugpy server is listening on 0.0.0.0:5678
[DEBUG] Waiting for debugger to attach...

となっているので、デバッグの起動を待機していることがわかる。 ちなみにこの状態ではREST APIにリクエストを投げても応答はない。

デバッグ起動

デバッグを起動するとログは

コンテナログ
INFO: Will watch for changes in these directories: ['/workspace']
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: Started reloader process [1] using WatchFiles
0.00s - Debugger warning: It seems that frozen modules are being used, which may
0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off
0.00s - to python to disable frozen modules.
0.00s - Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation.
[DEBUG] Debugpy server is listening on 0.0.0.0:5678
[DEBUG] Waiting for debugger to attach...
[DEBUG] Debugger attached. Continuing execution...

となり、デバッグがアタッチできたことがわかる

VScodeでは指定したブレークポイントでデバッグが止まる
2024-12-09-17-18-21

EABLE_DEBUGPY=true、DEFAULT_DEBUG=falseの場合

デバッグモードでは起動するが初期デバッグではないため
アプリケーションの起動部分のブレークポイントは動作しない。
※その以降の部分では動作する

EABLE_DEBUGPY=false、DEFAULT_DEBUG=trueの場合

デバッグ実行しない

EABLE_DEBUGPY=false、DEFAULT_DEBUG=falseの場合

デバッグ実行しない

アプリケーション起動部分のデバッグの再実行について

アプリケーション起動部分はuvicorn起動時しか動作しないため
再度デバッグをやり直す場合はuvicornを再起動する必要がある。

方法としては

  1. コンテナ内でuvicornのプロセスをkillして再度起動
  2. コンテナ自体を再起動
  3. ソースを変更してreloadさせて再起動

がある。
dockerfileでuvicornをreloadオプションをつけて起動している場合は 3が一番手っ取り早い。

まとめ

コンテナのアプリをローカルからリモートでデバッグする
方法をまとめた。

コンテナ開発は便利だが、コンテナ内部で開発をするのは
少し面倒だと感じていた(割と動作が遅い)ので、
これでストレスなく開発できそう。

また

  1. サーバー起動でポートを開けるか
  2. コード内でポートを開けるか

については、起動部分の動作が担保されている既存のアプリケーションでは
①を使う。開発中などでは②を使うのがいいかと思う。

参考

新着記事

目次
タグ別一覧
top