3、vLLM基础镜像制作:Dockerfile编写、多阶段构建、镜像瘦身技巧
好,咱们进入第三章。镜像制作这事儿,说白了就是给vLLM搭个“窝”。窝搭得太大,传输慢、启动慢;窝搭得太糙,跑起来各种报错。我刚开始搞K8s部署时,就吃过镜像太大的亏——一个镜像几个G,拉取半小时,Pod调度都卡成PPT。后来才明白,镜像瘦身不是炫技,是生产环境的刚需。
3.1 为什么需要自制基础镜像?
官方镜像能用吗?能用。但生产环境里,你总会遇到这些情况:
- 需要集成自定义的tokenizer或预处理逻辑
- 要打安全补丁、装监控agent
- 想控制CUDA版本、Python版本、系统库版本
- 镜像太大,需要针对特定硬件做裁剪
嗯,说白了,官方镜像是“通用款”,咱们要的是“定制款”。
3.2 Dockerfile编写:从零搭一个vLLM镜像
先看一个我常用的Dockerfile模板。这个版本我迭代过十几次,踩了不少坑。
# ========== 第一阶段:构建阶段 ==========
FROM nvidia/cuda:12.1.0-runtime-ubuntu22.04 AS builder
# 我个人习惯用apt装基础工具,不用miniconda
# 因为conda的依赖解析太慢,而且镜像层数多
RUN apt-get update && apt-get install -y \
python3.10 python3.10-dev python3-pip \
git build-essential \
&& rm -rf /var/lib/apt/lists/*
# 设置Python软链,避免版本混乱
RUN ln -sf /usr/bin/python3.10 /usr/bin/python
# 先装PyTorch,再装vLLM
# 注意:CUDA版本要和宿主机驱动匹配
RUN pip install --no-cache-dir torch==2.1.0 --index-url https://download.pytorch.org/whl/cu121
# 安装vLLM及其依赖
# 这里用--no-build-isolation可以加速,但要注意依赖冲突
COPY requirements.txt .
RUN pip install --no-cache-dir --no-build-isolation -r requirements.txt
# ========== 第二阶段:运行阶段 ==========
FROM nvidia/cuda:12.1.0-runtime-ubuntu22.04
# 只复制必要的Python环境和vLLM包
COPY --from=builder /usr/local/lib/python3.10/dist-packages /usr/local/lib/python3.10/dist-packages
COPY --from=builder /usr/local/bin /usr/local/bin
# 创建非root用户,安全第一
RUN useradd -m -u 1000 vllm && \
mkdir -p /models /cache && \
chown -R vllm:vllm /models /cache
USER vllm
WORKDIR /app
# 暴露vLLM默认端口
EXPOSE 8000
# 入口脚本,支持动态配置
COPY entrypoint.sh .
ENTRYPOINT ["bash", "entrypoint.sh"]
我的小技巧:requirements.txt里不要写死版本号,用“>=”加下限。比如
vllm>=0.4.0。这样后续升级小版本时不用改Dockerfile,重新构建就行。
3.3 多阶段构建:为什么是两阶段?
你想想看,一个完整的vLLM编译环境有多大?CUDA工具链、gcc、cmake、各种头文件……加起来轻松超过5GB。但运行时只需要编译好的.so文件和Python包。
多阶段构建的核心思想就是:编译和运行分离。
- 第一阶段(builder):装所有编译工具、下载源码、编译。这个阶段可以很“胖”。
- 第二阶段(runtime):只复制编译产物和运行时依赖。这个阶段要“瘦”。
我曾经犯过一个错:把builder阶段的apt缓存也复制到了runtime阶段。结果镜像里多了几百MB的deb包缓存。后来加了rm -rf /var/lib/apt/lists/*才解决。
3.4 镜像瘦身技巧:从5GB到1.2GB
嗯,这里才是重点。我总结了几条实战经验:
| 技巧 | 说明 | 效果 |
|---|---|---|
| 多阶段构建 | 编译和运行分离 | 减少2-3GB |
| 清理apt缓存 | 每层apt install后都清理 | 减少200-500MB |
| 使用--no-cache-dir | pip不缓存wheel包 | 减少300-800MB |
| 合并RUN指令 | 减少镜像层数 | 减少100-200MB |
| 删除不必要的CUDA组件 | 只保留runtime,不要devel | 减少1-2GB |
| 使用slim基础镜像 | 比如python:3.10-slim | 减少300-500MB |
核心原则:镜像里只放“运行时必须”的东西。任何编译工具、文档、缓存、测试数据,统统不要。
3.5 避坑指南:我踩过的三个坑
讲几个真实案例,你遇到了能少走弯路:
- CUDA版本不匹配:我曾经用CUDA 11.8的镜像,但宿主机驱动只支持12.0。结果vLLM启动时直接报“CUDA driver version is insufficient”。后来我统一用
nvidia-smi先查驱动版本,再选对应的CUDA镜像。 - Python版本冲突:系统自带的Python 3.8和vLLM要求的3.10打架。我一开始用update-alternatives切换,但总出问题。后来干脆在Dockerfile里硬编码
python3.10,省心。 - 镜像层数过多:每个RUN指令都会生成一层。我有个同事一个Dockerfile写了20个RUN,镜像构建慢得像蜗牛。合并成3-4个RUN后,构建时间从15分钟降到3分钟。
特别注意:不要用
apt-get upgrade升级所有包!这会把基础镜像的依赖关系搞乱,而且每次构建都会拉新版本,导致镜像不可复现。只升级你需要的安全补丁即可。
3.6 验证镜像:构建完怎么测?
镜像构建完,别急着推仓库。先本地跑一下:
# 构建镜像
docker build -t vllm-custom:1.0 .
# 查看镜像大小
docker images vllm-custom:1.0
# 启动容器,测试vLLM能否正常加载
docker run --gpus all -p 8000:8000 \
-v /path/to/models:/models \
vllm-custom:1.0 \
--model /models/llama-7b \
--port 8000
# 用curl测试API
curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{"model": "default", "prompt": "Hello", "max_tokens": 50}'
如果这一步能跑通,镜像基本就稳了。我习惯在CI/CD流程里加这一步,避免推到仓库才发现问题。
3.7 本章知识体系
下面这张图,是我梳理的镜像制作核心逻辑。你看一眼,心里就有谱了:
这张图把整个流程串起来了。你照着这个思路写Dockerfile,基本不会跑偏。
最后说一句:镜像瘦身不是一锤子买卖。每次升级vLLM版本、换CUDA版本,都要重新审视镜像大小。我习惯在CI里加一个“镜像大小检查”,超过2GB就告警。这样能及时发现“镜像膨胀”的问题。
好,这一章就到这里。镜像做好了,下一章咱们聊聊怎么把它部署到K8s上,以及怎么管理多个模型版本。
公众号:蓝海资料掘金营,微信deep3321