3. Docker与容器基础
大模型从训练到推理部署,本质上是一系列复杂软件环境(CUDA、cuDNN、Python 依赖、推理框架如 vLLM/TensorRT-LLM、模型权重文件)的打包与交付过程。Docker 容器技术通过将应用及其依赖封装为标准化、可移植的镜像,解决了“在我机器上能跑”的经典难题。在 Kubernetes 集群中,Pod 是最小调度单元,而 Pod 内的容器则直接由容器运行时(如 containerd)管理。因此,理解 Docker 的镜像构建、容器生命周期与资源隔离机制,是掌握 Kubernetes 节点资源调度、自动扩缩容和故障隔离的基石。本章将聚焦于这三个核心维度,为后续在集群中部署大模型推理服务奠定坚实的操作与原理基础。
上图展示了 Docker 三大核心环节与 Kubernetes 的衔接关系。镜像构建阶段产生不可变的交付物;容器运行阶段将镜像实例化为隔离的进程环境;资源限制阶段则通过 Linux 内核的 cgroup 机制为容器划定 CPU、内存、GPU 等硬性边界。在 Kubernetes 中,这三个环节被抽象为 Image、Container 和 Resources 配置,由 kubelet 统一协调。
3.1 镜像构建:从 Dockerfile 到可交付的推理环境
3.1.1 核心原理:分层文件系统与联合挂载
Docker 镜像由多个只读层(Layer)叠加而成,每一层对应 Dockerfile 中的一条指令。这种设计使得镜像可以复用公共层(如 CUDA 基础镜像),大幅节省存储空间和网络传输时间。当容器启动时,Docker 在镜像层之上添加一个可写层(Container Layer),所有运行时修改(如写入日志、生成临时文件)都发生在此层,容器删除后该层随之消失。
对于大模型镜像,层设计尤为关键。一个典型的推理镜像包含以下层次:
- 基础层:nvidia/cuda:12.1.1-cudnn8-devel-ubuntu22.04(约 4.5GB)
- 系统依赖层:gcc、python3.10、curl 等(约 300MB)
- Python 包层:torch、transformers、vllm 等(约 2.5GB)
- 应用代码层:推理服务脚本、配置文件(约 50MB)
- 模型权重层:Llama-2-7B 量化模型(约 4GB,通常通过数据卷挂载而非打入镜像)
3.1.2 操作流程:编写高效 Dockerfile 的五个关键步骤
- 选择合适的基础镜像:优先使用官方 nvidia/cuda 镜像,并明确 tag(如 12.1.1-cudnn8-devel-ubuntu22.04),避免使用 latest。
- 利用构建缓存:将变化频率低的指令(如 apt 安装、pip 安装)放在 Dockerfile 前部,变化频繁的(如代码 COPY)放在后部。
- 多阶段构建:在 builder 阶段编译依赖,在 runtime 阶段仅保留运行所需文件,显著减小镜像体积。
- 设置非 root 用户:创建专用用户运行推理服务,增强安全性。
- 配置健康检查:使用 HEALTHCHECK 指令定义容器存活探针,便于 K8s 感知服务状态。
# 多阶段构建示例:大模型推理镜像
# 阶段1:编译依赖
FROM nvidia/cuda:12.1.1-cudnn8-devel-ubuntu22.04 AS builder
RUN apt-get update && apt-get install -y python3.10 python3-pip git
RUN pip3 install torch==2.1.0 vllm==0.3.1 --index-url https://download.pytorch.org/whl/cu121
# 阶段2:运行时镜像
FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04
COPY --from=builder /usr/local/lib/python3.10/dist-packages /usr/local/lib/python3.10/dist-packages
COPY --from=builder /usr/local/bin/vllm /usr/local/bin/vllm
RUN useradd -m -u 1000 inference && mkdir /models && chown inference:inference /models
USER inference
WORKDIR /app
COPY --chown=inference:inference ./server.py /app/server.py
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --retries=3 CMD curl -f http://localhost:8000/health || exit 1
ENTRYPOINT ["python3", "/app/server.py"]
3.1.3 案例拆解:构建一个 7B 模型推理镜像的优化过程
背景与约束:某团队需要为 Llama-2-7B 模型构建推理镜像,目标部署到 4 节点 GPU 集群。初始镜像体积高达 18GB,导致每次发布需传输 5 分钟以上,且镜像仓库存储压力大。
分析判断:通过 docker history 分析,发现主要体积来自:① 基础镜像中的 devel 组件(编译器、头文件)占 3GB;② pip 安装时保留了缓存和 .pyc 文件;③ 模型权重被误打入镜像(8GB)。
优化操作:
- 将基础镜像从 devel 切换为 runtime 版本,减少 2.8GB。
- 在 pip 安装后执行
rm -rf ~/.cache/pip清理缓存。 - 将模型权重从镜像中移除,改用 NFS 挂载至 /models 目录。
- 采用多阶段构建,仅复制必要的 Python 包。
结果对比:优化后镜像体积降至 6.2GB,传输时间缩短至 1 分钟以内,镜像构建时间从 15 分钟降至 6 分钟。同时,由于权重独立管理,模型更新无需重新构建镜像。
docker build --no-cache 强制重建所有层,这会显著增加 CI 时间。应合理利用缓存,仅在依赖变更时失效相关层。
3.2 容器运行:从镜像到隔离的推理进程
3.2.1 核心原理:命名空间与进程视图隔离
容器本质上是宿主机上的普通进程,通过 Linux 命名空间(Namespace)实现隔离。Docker 默认启用以下命名空间:
| 命名空间 | 隔离内容 | 大模型场景意义 |
|---|---|---|
| PID | 进程编号 | 容器内 PID 1 为推理服务主进程,便于信号管理 |
| Network | 网络栈(IP、端口) | 容器独立 IP,端口映射避免冲突 |
| Mount | 文件系统挂载点 | 模型权重卷挂载,不影响宿主机目录 |
| UTS | 主机名 | 容器独立 hostname,便于日志区分 |
| IPC | 进程间通信 | 防止跨容器共享内存干扰 |
| User | 用户 ID | 容器内 root 映射为宿主机非特权用户 |
当执行 docker run 时,Docker 守护进程会调用 runc 创建容器,runc 负责配置命名空间和 cgroup,然后启动容器进程。在 Kubernetes 中,kubelet 通过 CRI 调用 containerd,containerd 再调用 runc,流程一致。
3.2.2 操作流程:启动大模型推理容器的关键参数
# 启动一个 vLLM 推理容器示例
docker run -d --name llama2-7b \
--gpus all \
-p 8000:8000 \
-v /data/models:/models:ro \
-e MODEL_NAME=/models/llama2-7b \
-e MAX_LENGTH=4096 \
--shm-size=8g \
--ulimit memlock=-1 \
--ulimit stack=67108864 \
--restart=unless-stopped \
registry.example.com/inference/llama2:1.2.0
参数说明:
--gpus all:暴露所有 GPU 给容器,需安装 NVIDIA Container Toolkit。-v /data/models:/models:ro:只读挂载模型目录,防止容器内误修改。--shm-size=8g:增大 /dev/shm 大小,避免 PyTorch DataLoader 多进程共享内存不足。--ulimit memlock=-1:允许锁定内存,防止 GPU 显存交换到系统内存。--restart=unless-stopped:容器异常退出时自动重启,但 K8s 中由 Pod 重启策略接管。
3.2.3 实战场景:容器启动失败的排障路径
初始现象:执行 docker run 后容器立即退出,docker logs 显示 CUDA error: out of memory。
分析过程:
- 检查 GPU 显存占用:
nvidia-smi发现已有其他容器占用 80% 显存。 - 确认容器请求的显存:vLLM 默认预分配全部可用显存,导致冲突。
- 解决方案:设置
--gpus '"device=0"'指定 GPU,并配置 vLLM 的--gpu-memory-utilization=0.6限制显存使用率。
复盘经验:在 GPU 共享场景下,必须显式指定 GPU 编号和显存上限,不能依赖默认行为。同时建议在容器启动脚本中增加显存预检逻辑。
3.3 资源限制:cgroup 与 GPU 隔离
3.3.1 核心原理:cgroup 控制组
Linux cgroup(Control Groups)是内核提供的资源隔离机制,Docker 和 Kubernetes 均依赖它来限制容器的 CPU、内存、磁盘 IO 等资源。cgroup v2 是当前主流版本,在 Kubernetes 1.25+ 中默认启用。
对于大模型推理,资源限制的核心目标:
- CPU 限制:防止推理进程占满所有 CPU 核,影响其他 Pod。
- 内存限制:防止内存泄漏导致宿主机 OOM,触发内核杀进程。
- GPU 显存限制:通过 NVIDIA 容器运行时将显存隔离,但需注意显存是独占资源,无法超卖。
3.3.2 操作流程:在 Docker 和 K8s 中配置资源限制
Docker 命令行方式:
docker run -d --name inference \
--cpus=4 \
--memory=16g \
--memory-swap=16g \
--pids-limit=512 \
--device=/dev/nvidia0 \
--gpus '"device=0"' \
inference-image:latest
Kubernetes Pod 声明方式:
apiVersion: v1
kind: Pod
metadata:
name: llama2-inference
spec:
containers:
- name: inference
image: registry.example.com/inference/llama2:1.2.0
resources:
requests:
cpu: 4
memory: 16Gi
nvidia.com/gpu: 1
limits:
cpu: 8
memory: 20Gi
nvidia.com/gpu: 1
在 K8s 中,requests 用于调度决策(保证资源),limits 用于运行时限制(硬上限)。当容器内存使用超过 limits 时,内核会触发 OOM Kill;当 CPU 超过 limits 时,会被限流(throttled)。
3.3.3 案例拆解:OOM 引发的推理服务中断与修复
背景:某生产环境部署了 8 个推理 Pod,每个 Pod 设置 memory limit 为 16Gi。运行一周后,部分 Pod 频繁重启,日志显示 Killed 信号。
关键指标分析:
- 通过
kubectl top pod发现内存使用率持续在 15.5Gi~16.2Gi 波动。 - 查看 cgroup 事件:
cat /sys/fs/cgroup/kubepods/podxxx/memory.events显示 oom_kill 计数增加。 - 进一步分析:vLLM 的 KV Cache 动态增长,加上 Python 内存碎片化,导致实际内存超出预期。
解决步骤:
- 将 memory limit 提升至 24Gi,并同步调整节点内存预留。
- 在 vLLM 启动参数中设置
--max-num-seqs=32限制并发序列数,控制 KV Cache 大小。 - 启用 Python 内存优化:设置
PYTHONMALLOC=malloc减少内存碎片。
结果:调整后 Pod 稳定运行 30 天无 OOM,内存使用率稳定在 18Gi 左右。
requests 与 limits 比例(通常 1:1.25 或 1:1.5)。
3.4 常见误区与生产实践建议
| 误区 | 后果 | 正确做法 |
|---|---|---|
| 镜像中打入模型权重 | 镜像体积巨大,发布慢,版本管理混乱 | 权重挂载到 PV 或对象存储 |
| 容器内以 root 运行 | 安全漏洞可导致宿主机被攻陷 | 创建非 root 用户,设置 USER 指令 |
| 不设置内存 limit | 内存泄漏拖垮整个节点 | 始终设置 limits,并监控实际使用 |
| GPU 显存不限制 | 多个容器争抢显存导致 OOM | 使用 NVIDIA 工具限制显存或分卡 |
| 忽略 /dev/shm 大小 | PyTorch DataLoader 报共享内存不足 | 设置 --shm-size=8g 或更大 |
在生产环境中,建议将 Docker 镜像构建纳入 CI/CD 流水线,使用 Trivy 或 Clair 进行漏洞扫描;容器运行时启用 seccomp 和 AppArmor 安全配置;资源限制配置通过 K8s 的 LimitRange 和 ResourceQuota 进行全局管控,避免每个团队自行定义导致集群资源碎片化。
掌握 Docker 的镜像构建、容器运行和资源限制三大基础能力,是理解 Kubernetes 调度器如何为 Pod 分配节点资源、如何通过 HPA(Horizontal Pod Autoscaler)基于 CPU/内存指标自动扩缩容的前提。下一阶段,我们将把这些容器化应用编排到 Kubernetes 集群中,实现大模型服务的高可用与弹性伸缩。