使用 Uvicorn 監聽 FastAPI 框架的 web server,但是 reload 卻失效?
前言
前陣子接到公司新的專案,打開專案發現 docker-compose.yml 都寫好了,很興奮直接 docker compose <service>
然後開始修改好一些商業邏輯,存檔後,以為 uvicorn 自動會 reload server。結果,存檔後甚麼事都沒有發生 !!!
接著,我把整個 docker image 刪掉重建,然後再重啟一個 contanier。疑好了?! 有吃到最新的商業邏輯,但我不會每次都要重建 image 吧? 實在是太麻煩 🤯
發現 uvicorn reload 的問題
我研究了一下 docker-compose.yml 到底做了甚麼事,其中我要用到的 service 如下:
1 | supplier-api: |
看起來該做的事都有做,我查了一些關鍵字像是 uvicorn reload not working,網路上有建議加上 ENV WATCHFILES_FORCE_POLLING=true
或是 reload-dir <檔案路徑>
等。我全部嘗試一輪後,發現還是無效。但是,倒是發現一個有趣的現象。
我的專案目前的架構如下: 我主要是用 supplier 這個服務。
1 | services |
當我改動 api/api
底下的檔案時,uvicorn reload 是會更新的。但是,當我改動 libs/feed
底下的檔案時,uvivorn reload 完全失效。即便我加上 reload-dir /libs/feed
,雖然 reload 會跳更新 ( 如下圖 ),代表有偵測到程式的變動,我進到 docker 容器裡檢查,程式碼也確實有更新,但是就是吃不到最新的商業邏輯,真的很詭異 👻
查了一大圈,ChatGPT 建議可以更新套件看看,這個專案是用 pdm 套件管理工具,所以我下 pdm install
指令,把所有套件更新,按下儲存後,server 居然 reload 成功了 !!
原來一切的問題就出在 libs/feed
這個資料夾,因為它被包成 feed
相依套件使用了。查了許久,加上 ChatGPT 的綜合回覆,uvicorn 在 reload 時,是無法更新套件的。
以下來自 ChatGPT 回覆:
While Uvicorn’s reload feature is very convenient for development by auto-reloading on code changes, it is not designed to handle dependency updates. Manual intervention is required to ensure that any new or updated dependencies are properly installed and reflected in the running application.
pdm 在更新套件時,會去讀專案的 pyproject.toml 檔,dependencies 就是 pdm 會下載的所有套件,但通常只有起容器的當下才會安裝。所以,在開發過程中,libs/feed
內的程式碼會不斷被更新,如果要吃到最新的商業邏輯,就必須一直更新 feed 這個相依套件,在開發上會非常不方便。
1 | [project] |
解方 - entr 套件
後來有找到一個套件 entr : 可以偵測文件的變動,並在變更時執行指定命令。這不正是我需要的嗎!!
我要在更動 libs/feed
中的程式碼的時候,執行 pdm install
,然後再交給 uvicorn reload server。
修改 Dockerfile
1
2
3RUN apt-get update && apt-get install entr
ENTRYPOINT ["/bin/bash", "-c"]
CMD ["find ../libs -name '*.py' | entr -n -r pdm install & python3 -m uvicorn api:fastapi_app --reload --host=0.0.0.0 --port=8001"]修改 docker-compose.yaml
docker-compose.yaml 1
2
3
4
5entrypoint: ["/bin/bash", "-c"]
command:
- |
find ../libs -name '*.py' | entr -n -r pdm install &
python3 -m uvicorn api:fastapi_app --reload --host=0.0.0.0 --port=8001 --log-config=log-config-dev.yamlfind ../libs -name '*.py'
: 使用 find 命令查找 ../libs 目錄中所有 .py 的文件,並將結果傳遞給 entr 命令。|
: 管道符號,用於將一個命令的輸出作為另一個命令的輸入,有一點因果關係。entr -n -r pdm install
: entr 接收 find 命令輸出的文件列表,並監視這些文件。如果有任何文件發生變更,entr 會執行 pdm install 命令。-n
: 這個選項確保 entr 在文件變更後不會立即執行命令,而是等待新的變更事件發生。這樣可以避免因為單一文件變更而多次執行命令。-r
: 這個選項指示 entr 每次檔案變更後都重新執行指定的命令。
修改後發現 WSL2 文件系統的問題
以下問題主要針對 windows 用戶。我修改了 Dockerfile 跟 docker-compose.yml,依然沒有 reload 成功,但是同事的 mac 是有成功的。我主要是用 WSL2 開發,後來在 這篇文章 找到問題,發現是 WSL2 文件系統的差異。
問題描述:
一直以來,我都是把文件存在 windows drive 的文件系統,也就是把專案放在/mnt/c
底下,但是 entr 套件監聽文件的變動,是用 Linux 的核心功能 inotify 的技術,但他並不支援 Windows 磁碟驅動器(如 C:\)上的文件系統。解法:
把專案移動到 Linux 的文件系統 ( ext4 ) 下,也就是home/user
底下,Linux 的文件系統才支援 inotify。補充:
windows 官方建議 如果開發上用較多 Linux 指令,建議把文件存在 Linux 的文件系統 ( ext4 ),也就是home/user
底下;反之,如果用較多 windows 指令,則建議將文件存放在 Windows 的文件系統,也就是/mnt/c
底下。雖然文件可以跨系統操作,但速度會變很慢。
官方說法如下:
Store your project files on the same operating system as the tools you plan to use.
For the fastest performance speed, store your files in the WSL file system if you are working on them with Linux tools in a Linux command line (Ubuntu, OpenSUSE, etc). If you’re working in a Windows command line (PowerShell, Command Prompt) with Windows tools, store your files in the Windows file system. Files can be accessed across the operating systems, but it may significantly slow down performance.
結論
- python uvicorn reload server 並不會針對相依套件自動更新,可以搭配 entr 的套件,針對文件變動時,下指令更新相依性套件。
- WSL2 若使用 windows 文件系統 ( mnt/c ),並不支援 inotify 技術,必須將專案移動到 Linux 文件系統下 ( ext4 )。