3、Docker与镜像构建:编写Dockerfile打包vLLM、构建镜像、推送至私有仓库、镜像拉取策略
好,咱们进入第三章。说实话,这一章是实操性最强的一环。你想想看,模型代码写得再好,部署的时候镜像拉不下来、环境不一致,那全白搭。我见过太多团队在「环境不一致」这个坑里反复打转。所以,把vLLM打包成Docker镜像,是走向生产环境的第一步。
3.1 为什么非要用Docker?
说白了,Docker解决的是「在我机器上能跑」的问题。vLLM依赖的CUDA版本、Python包、系统库,稍微差一个版本就可能报错。我个人的习惯是:任何需要重复部署的服务,第一件事就是写Dockerfile。你想想看,有了镜像,不管是在开发机、测试机还是生产集群,拉下来就能跑,多省心。
核心原则:镜像即环境,环境即代码。把环境固化到镜像里,杜绝「手动装依赖」这种原始操作。
3.2 编写Dockerfile:从零开始打包vLLM
咱们直接上代码。下面这个Dockerfile是我在项目中反复打磨过的版本,兼顾了镜像大小和构建速度。
# 使用官方CUDA 12.1基础镜像,vLLM对CUDA版本有要求
FROM nvidia/cuda:12.1.0-runtime-ubuntu22.04
# 设置工作目录
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
python3.10 \
python3-pip \
git \
&& rm -rf /var/lib/apt/lists/*
# 设置Python软链接
RUN ln -s /usr/bin/python3.10 /usr/bin/python
# 安装vLLM及其依赖
# 注意:这里指定版本,避免生产环境出现意外
RUN pip install --no-cache-dir \
vllm==0.4.2 \
torch==2.1.2 \
transformers==4.36.2
# 复制启动脚本
COPY entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
# 暴露API端口
EXPOSE 8000
# 设置默认启动命令
ENTRYPOINT ["/app/entrypoint.sh"]
我的经验:曾经有一次我偷懒没指定vLLM版本,结果第二天镜像构建出来的是新版本,接口变了,整个服务挂了半小时。从那以后,我所有Dockerfile里的包都锁死版本号。
3.3 构建镜像:别小看这一步
Dockerfile写好了,接下来就是构建。命令很简单,但有几个坑我得提醒你。
# 构建镜像,注意最后的点不能少
docker build -t vllm-server:1.0.0 .
# 如果你在构建过程中需要查看日志
docker build --progress=plain -t vllm-server:1.0.0 .
为什么会推荐加 --progress=plain?因为默认的构建输出太简洁了,一旦报错你根本不知道哪一步出了问题。我刚开始用Docker时,就因为这个浪费了大半天时间。
注意:构建镜像时,确保你的网络能访问到Docker Hub和PyPI。如果在内网环境,记得配置代理或者使用镜像源。我曾经在内网构建时,卡在pip install这一步整整两个小时,最后发现是没配国内镜像源。
3.4 推送至私有仓库:生产环境的标配
镜像构建好了,不能只留在本地。生产环境里,我们通常把镜像推送到私有仓库。这里以Harbor为例。
# 登录私有仓库
docker login harbor.internal.com
# 给镜像打标签,格式:仓库地址/项目名/镜像名:版本
docker tag vllm-server:1.0.0 harbor.internal.com/ai-team/vllm-server:1.0.0
# 推送镜像
docker push harbor.internal.com/ai-team/vllm-server:1.0.0
| 操作 | 命令 | 说明 |
|---|---|---|
| 登录 | docker login [仓库地址] |
输入用户名和密码 |
| 打标签 | docker tag [原镜像] [新标签] |
新标签必须包含仓库地址 |
| 推送 | docker push [镜像标签] |
确保网络连通 |
避坑指南:我曾经在推送时忘记打标签,直接push了本地镜像名,结果推到了Docker Hub公共仓库。还好发现得早,不然模型权重就公开了。记住:私有仓库的镜像名必须包含仓库地址前缀。
3.5 镜像拉取策略:Kubernetes里的关键配置
镜像推送到仓库了,Kubernetes怎么拉取?这里有个关键参数:imagePullPolicy。我见过不少新手在这里翻车。
apiVersion: v1
kind: Pod
metadata:
name: vllm-pod
spec:
containers:
- name: vllm
image: harbor.internal.com/ai-team/vllm-server:1.0.0
imagePullPolicy: IfNotPresent # 关键配置
三种拉取策略的区别,我整理了一张表:
| 策略 | 行为 | 适用场景 |
|---|---|---|
Always |
每次启动都从仓库拉取 | 开发测试环境,确保最新代码 |
IfNotPresent |
本地没有时才拉取 | 生产环境,避免重复拉取 |
Never |
只使用本地镜像 | 离线环境或特殊调试 |
我的建议:生产环境用 IfNotPresent。为什么?因为 Always 会导致每次Pod重启都去拉镜像,增加网络开销和启动时间。但如果你在频繁更新版本,可以临时改成 Always,上线稳定后再改回来。
3.6 本章知识体系图
下面这张图帮你理清整个流程:从编写Dockerfile到最终在Kubernetes中运行。
嗯,这张图把整个流程串起来了。你从左边开始,一步步走到右边,最后进入Kubernetes集群。每一步都有它的意义,缺一不可。
3.7 小结
这一章我们走完了从Dockerfile到Kubernetes拉取镜像的完整链路。我个人觉得,最核心的是两点:一是把环境固化到镜像里,二是选对拉取策略。前者保证一致性,后者保证效率。你在实际项目中,可以先把Dockerfile写好,构建一次,推送到私有仓库,然后在Kubernetes里用 IfNotPresent 策略拉取。这套流程跑通了,后续的模型部署就顺畅多了。