当サイトは、アフィリエイト広告を利用しています
dockerコンテナ内のuvicornサーバーで起動しているFastAPIアプリケーションを
ローカルのVSCodeからリモートデバッグする方法を調べたので忘備録として残す。
当記事では
についてまとめる
VSCode上でFastAPIアプリケーションをdockerコンテナを使って
開発している場合、デバッグ方法としては
がある。
こちらの方法はVSCodeからコンテナに接続しコンテナ上でデバッグを行う。
コンテナ上でUvicornをデバッグモードで起動してデバッグするイメージ。
※コンテナに接続後に手動でデバッグモードでUvicornを起動する必要がある。
方法については下記記事で紹介しているのでご参照ください
こちらの方法はコンテナで起動しているUvicorn上のFastAPIアプリに対して
ローカルからリモートでアタッチしてデバッグする。
下記のようなイメージ
今回はこっちの方法を解説する。
リモートデバッグのメリットとしては
「コンテナ用VSCodeで開く必要がない」
というのがある。
コンテナに接続してコンテナ上でデバッグする場合、コンテナ用のVSCodeを開く
必要があるが
と、意外とめんどくさいし時間がかかる。
リモートデバッグでローカルのVSCodeからリモートで
デバッグするので無駄な時間が省ける。
サンプルのFastAPIのREST APIをコンテナ上のuvicornで実行し
デバッグする手順をまとめる。
リモートデバッグは「debugpy」というパッケージを使って行う
Pythonアプリケーションのリモートデバッグを可能にするパッケージで
VSCodeのデバッガクライアントと連携して、Pythonプログラムのデバッグをサポートする。
このパッケージを使って、リモート環境で実行されているコンテナのアプリケーションに対して
デバッグする。
全体の動作としては下記のようなイメージ。
上記のような仕組みのためリモートデバッグはデバッグする対象にdebugpyが接続するため はポートを開けてやる必要がある。
その方法としては
の2パターンがある
まずはサーバー起動時にdebugpyが接続するためのポートを空ける方法をまとめる。
この方法のメリットは
になる。
逆にデメリットとしては
がある。
※詳しくはdockerfileに記載。
下記の環境で行う
全体的なプロジェクト構成は下記のようにする
.|-- .vscode| `-- launch.json|-- app| |-- __init__.py| |-- asgi.py| |-- endpoints.py| `-- main.py|-- docker-compose.yml|-- dockerfile`-- requirements.txt
動かすのに最低限の構成。
各ファイルを解説する
fastapi==0.115.6uvicorn[standard]==0.32.1debugpy==1.8.9
必要なパッケージをインストールする
FROM python:3.12# PYTHONPATHの設定ENV PYTHONPATH=/workspace/app# workspaceディレクトリ作成、移動WORKDIR /workspace# プロジェクトディレクトリにコピーCOPY requirements.txt /workspace# 必要パッケージのインストールRUN pip install --upgrade pipRUN 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に指定したコマンドがコンテナ起動時に実行される
debugpyによるリモートデバッグのためのサーバー(ポート 5678)を起動する。
このコマンドに続けて実行したいモジュールやスクリプトを指定することで、デバッグ対象を起動できる
debugpyがラップする対象として uvicornモジュールを指定しているので
debugpyの機能が有効な状態でuvicornが起動する
debugpy を -m フラグでコマンドとして使用すると、指定されたモジュール(ここでは uvicorn)の実行をそのままラップする。
この場合、デバッガがアタッチできるのはラップされたモジュールの中に限られるためuvicorn自体の
起動部分やその前の処理にはアクセスできないためデバッグ不可となる。
具体的にはアプリケーションの起動部分である
のデバッグができない。
version: "3"services:fastapi:container_name: "fastapi"build:context: .dockerfile: Dockerfilevolumes:- .:/workspacetty: trueports:- 8000:8000 # ホストマシンのポート8000を、docker内のポート8000に接続する- 5678:5678 # debugpリモートデバッグ用のポート
エンドポイントを管理するモジュール
from fastapi import APIRouterfrom fastapi.responses import JSONResponserouter = 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でディクショナリリストを返却する
アプリケーションの初期化処理を行うモジュール。
from fastapi import FastAPIimport endpoints as endpointsdef create_app():# アプリケーションインスタンスの作成app = FastAPI()# ルーティング設定app.include_router(endpoints.router)return app
FastApiのアプリケーションインスタンスを生成して
エンドポイントを設定後、返却する関数を定義する。
FastApiアプリケーションをASGIサーバーで起動するための エントリーポイントとなるモジュール
from main import create_appapp = create_app()
Uvicorn起動時にこのFastApiアプリケーションインスタンスまでのパスを指定して 起動することでUvicornでFaskApiアプリケーションを動かすことができる。
dockerfileのCMDの「app.asgi:app」はこのモジュールのappを指定している
VScodeのデバッガの設定ファイル
{"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で
volumes:- .:/workspace
のようにVSCodeで開いているルートフォルダとコンテナの「/workspace」を
バインドマウントしているので、それに合わせている。
設定は完了したのでVSCodeからリモートデバッグをしてみる
ターミナルからコマンドをたたいてコンテナを起動する
docker-compose up -d
コンテナが起動していることを確認する
docker ps
起動している場合は起動中のコンテナが表示される
$ docker logs <コンテナのID>0.01s - Debugger warning: It seems that frozen modules are being used, which may0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off0.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 WatchFiles0.01s - Debugger warning: It seems that frozen modules are being used, which may0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off0.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 may0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off0.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のデバッグを起動して、コンテナのUvicornで動作する
FastAPIアプリに対して、ローカルからリモートでデバッグをする
デバッグタブでlaunch.jsonで設定したデバッガを指定して起動する ※F5でも起動できる
任意の場所にブレークポイントを設定し
REST APIにリクエストすると止まるのを確認
$ curl -X GET http://localhost:8000
dockerfileでCMDでUvicornにreloadオプションをつけて起動しているので
ソースを変更した場合は、再起動され、変更が反映される。
ソースを変更した際のdockerのlogを確認すると
WARNING: WatchFiles detected changes in 'app/endpoints.py'. Reloading...INFO: Shutting downINFO: 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 may0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off0.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.
のように再起動されている。
※デバッガー起動中であっても可能
こちらはソース自体にポートを開けるコードを仕込む。
この方法のメリットは
になる。
逆にデメリットとしては
がある。
ベースは「サーバー起動時にコマンドでポートを空ける方法」のコードを使うので
変更点のみまとめる
新規追加ファイルは
変更ファイルは
になる。
EABLE_DEBUGPY=true # デバッグモード設定DEFAULT_DEBUG=true # 初期デバッグ設定
環境変数を二つ設定する
デバッグモードでアプリケーションを起動するかの設定
「true」の場合はデバッグモードで起動する
起動初期からデバッグするかの設定。
これをtrueにした場合は、デバッグが起動するまでアプリケーションの起動を
待機する。
つまりこれをtrueにした場合、uvicorn起動部分のデバッグ可能になる
import osclass Config:# デバッグモードEABLE_DEBUGPY = os.environ.get('EABLE_DEBUGPY')# 初期デバッグDEFAULT_DEBUG = os.environ.get('DEFAULT_DEBUG')config = Config() # グローバルインスタンス
環境変数をプログラム内でグローバルインスタンスとして保持させ
他のプログラムからはConfigを通じて参照するようにする
FROM python:3.12# PYTHONPATHの設定ENV PYTHONPATH=/workspace/app# workspaceディレクトリ作成、移動WORKDIR /workspace# プロジェクトディレクトリにコピーCOPY requirements.txt /workspace# 必要パッケージのインストールRUN pip install --upgrade pipRUN pip install -r requirements.txt# uvcornサーバーを起動(debugpyを使わない)CMD ["uvicorn", "asgi:app", "--reload", "--host", "0.0.0.0", "--port", "8000"]
uvicornをdebugpyを使わずに起動する。
※debugpyのためのポート開放はコードでするため。
version: "3"services:fastapi:container_name: "fastapi"build:context: .dockerfile: Dockerfilevolumes:- .:/workspacetty: trueenv_file:- .envports:- 8000:8000 # ホストマシンのポート8000を、docker内のポート8000に接続する- 5678:5678 # debugpyリモートデバッグ用のポート
コンテナ起動時にコンテナに環境変数を.envファイルから設定する
from fastapi import FastAPIimport endpoints as endpointsfrom config import Configdef create_app():# デバッグモード判定if Config.EABLE_DEBUGPY == "true":import debugpy# デバッグサーバー起動debugpy_port = 5678debugpy.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から取得し
その値によって
を判定している。
リモートデバッグを実行する。
デバッグモードで起動し、初期デバッグも行う。
アプリケーションの起動部分でブレークポイントを設定 ※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 WatchFiles0.00s - Debugger warning: It seems that frozen modules are being used, which may0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off0.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 WatchFiles0.00s - Debugger warning: It seems that frozen modules are being used, which may0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off0.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...
となり、デバッグがアタッチできたことがわかる
デバッグモードでは起動するが初期デバッグではないため
アプリケーションの起動部分のブレークポイントは動作しない。
※その以降の部分では動作する
デバッグ実行しない
デバッグ実行しない
アプリケーション起動部分はuvicorn起動時しか動作しないため
再度デバッグをやり直す場合はuvicornを再起動する必要がある。
方法としては
がある。
dockerfileでuvicornをreloadオプションをつけて起動している場合は
3が一番手っ取り早い。
コンテナのアプリをローカルからリモートでデバッグする
方法をまとめた。
コンテナ開発は便利だが、コンテナ内部で開発をするのは
少し面倒だと感じていた(割と動作が遅い)ので、
これでストレスなく開発できそう。
また
については、起動部分の動作が担保されている既存のアプリケーションでは
①を使う。開発中などでは②を使うのがいいかと思う。