这两天我用 Python 给下载站开发了一个防盗链小网关,虽然项目不算重量级,但也涉及FastAPI之类的一大堆依赖。接下来一段时间内,项目的代码会快速迭代,依赖也可能不断更新,如果为了容器化,用 Dockerfile 执行完整的镜像构建流程,绝对会严重拖慢交付速度。即使正确执行了分层构建也没差,只要一动requirement列表,那么pip install这层缓存全部完蛋——应该不会有神经病把每个依赖都分层构建和缓存吧,不会吧?
那么有什么解决方法呢?
既然项目代码和依赖都会不断变化,那么最简单的部署方法其实是直接使用纯净的Python容器镜像,但是要注意将Python依赖也持久化到本地,不然每次运行容器都要重新从pypi拉取一次依赖,同样效率低下。这里Python和uv的容器分别有各自的解决方案。
Python容器通过PYTHONPATH定义额外的第三方库搜索目录(它会被添加到 sys.path,跟在当前脚本所在目录之后),因此在使用Python镜像时,我们要做的就包括:
- 指定pip将依赖安装到一个被持久化/挂载到卷的目录
- 指定Python在这个目录寻找第三方库
如此就得到了以下Docker compose模板(Docker CLI自行修改,都是一一对应的)
services:
myapp:
image: python:3.11.9
working_dir: /app
restart: always
volumes:
- ./app:/app
- ./packages:/packages
environment:
- PYTHONPATH=/packages/lib/python3.11/site-packages
command: bash -c "pip install --target=/packages/lib/python3.11/site-packages -r requirements.txt --exists-action=s && python main.py"
由于pip不会自动清理未列出的库,因此此前按照的所有package都会残留下来,不仅臃肿,而且指不定那天出现依赖冲突,因此需要不定期删档重开,不甚雅观。
如果使用的是uv,并且项目已经跟踪了pyproject.toml,那就更简单了,uv sync会在项目目录下创建.venv目录隔离环境,即使目录丢失,在缓存具备的情况下也能几乎在瞬间从缓存中同步依赖,因此我们直接增加一条,覆写并持久化UV_CACHE_DIR即可:
services:
myapp:
image: astral/uv:python3.12-bookworm-slim
restart: always
volumes:
- ./app:/app
- ./.cache/uv:/root/.cache/uv
environment:
- UV_CACHE_DIR=/root/.cache/uv
working_dir: /app
command: ["bash", "-c", "uv sync && uv run main.py"]
说实话,要是项目的Python版本固定、部署设备的CPU指令集固定,这玩意儿比每次都费一大堆时间build镜像来得靠谱多了,git pull完直接删除容器重建,基本不会有可感知的延迟,同时也不需要下载一堆乱七八糟的层(TMD服务器空间不够清理悬空镜像和层简直就是地狱),对于快速迭代的开发调试极为友好。
当然,生产环境里还是乖乖build吧。
1 个帖子 - 1 位参与者