AI Infra 学习路线 · 阶段一 · 模块三(下半部分)= 阶段一毕业项目
目标:把训练代码打包成镜像 → load 进集群 → 写 Pod 跑训练 → kubectl logs 看 loss 下降
环境:Windows + WSL2 + Docker Desktop + minikube + PyTorch (CPU 版)
0. 关于 GPU 透传:为什么这步用 CPU 而非 GPU(重要决策记录)
原计划是在 minikube 里跑 GPU 训练,但经过调研后决定暂缓 GPU 透传,改用 CPU 跑通整条编排链路。原因:
- 多层透传太脆弱:GPU 要穿透 物理卡 → WSL2(靠 /dev/dxg 桥接)→ Docker Desktop → minikube 节点容器 → 训练 Pod。模块二已打通前三层,但后两层在 WSL+Docker 驱动下官方支持时好时坏。
- device plugin 缺 NVML 库:WSL2 下把 GPU 支持延伸到 K8s(GPU Operator / device plugin)需要额外配置,常见错误是 plugin 容器找不到 NVML 库,需特殊处理才能让底层 GPU 库在容器里可见。
- Blackwell 架构(RTX 5060 Ti)在 WSL 下有已知 CUDA 兼容问题:出现过 nvidia-smi 正常但 CUDA 对应用不可用的情况。即便透传成功也可能在 PyTorch 调 CUDA 这最后一关失败。
- 社区经验 :踩通这条路的人最后大多建议绕开 Docker Desktop,改用原生 Docker(WSL 装 systemd + 原生 Docker + nvidia-ctk)或原生 Ubuntu。
结论 :用 CPU 跑通"K8s 编排训练任务"的完整链路(核心可迁移技能),GPU 调度的 YAML 写法用讲解补充。真正的 GPU 训练等将来上云租多卡机器、或切换到干净的原生环境时再做------那时环境干净反而顺。CPU 训练和 GPU 训练在 K8s 里的编排结构完全一样,只是底层算力不同。
GPU 调度的 Pod 写法(供将来参考):在容器 spec 里加
yamlresources: limits: nvidia.com/gpu: 1 # 申请 1 张 GPU前提是集群装好了 NVIDIA device plugin / gpu-operator。
1. 整体流程(容器化训练的标准节奏)
写训练代码 → 写 Dockerfile 打包 → docker build 构建镜像
→ minikube image load 进集群 → 写 Pod YAML → kubectl apply → kubectl logs 看结果
改代码后必须重新走 build + load(见第 5 节的坑),这是容器化开发的核心循环。
2. 训练代码 train.py
最小 PyTorch 训练骨架(随机数据,不下载数据集,避免网络坑):
python
import torch
import torch.nn as nn
model = nn.Sequential( # 注意拼写!Sequential 不是 Squential
nn.Linear(10, 64),
nn.ReLU(),
nn.Linear(64, 1)
)
X = torch.randn(512, 10) # 随机训练数据
y = torch.randn(512, 1)
loss_fn = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
print("训练开始", flush=True)
for epoch in range(50):
pred = model(X) # 前向
loss = loss_fn(pred, y) # 算损失
optimizer.zero_grad() # 清梯度
loss.backward() # 反向传播
optimizer.step() # 更新参数
if (epoch + 1) % 5 == 0:
print(f"Epoch {epoch+1:3d} | Loss: {loss.item():.4f}", flush=True)
print("训练完成", flush=True)
关键细节 flush=True :保证日志实时输出,否则在容器里可能被缓存,kubectl logs 看不到实时进度。
训练骨架记牢:前向 → 算 loss → 反向 → 更新,循环。换成大模型/多卡结构不变。
3. Dockerfile
dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY train.py .
# CPU 版 PyTorch(体积小很多)
RUN pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu
CMD ["python", "train.py"]
构建:
bash
docker build -t pytorch-train:v1 . # 装 PyTorch 那层下载几百 MB,可能慢
若装 PyTorch 太慢,可换国内源(清华:https://pypi.tuna.tsinghua.edu.cn/simple 配合官方 cpu whl,或找国内 torch 镜像)。
4. load 进集群 + 写 Pod 跑训练
bash
minikube image load pytorch-train:v1 # 桥梁:宿主机 Docker → minikube 集群
minikube image ls | grep pytorch # 确认(全名是 docker.io/library/pytorch-train:v1)
train-pod.yaml:
yaml
apiVersion: v1
kind: Pod
metadata:
name: pytorch-train
spec:
restartPolicy: Never # 关键:训练跑完就结束,不要自动重启
containers:
- name: trainer
image: pytorch-train:v1
imagePullPolicy: IfNotPresent
restartPolicy: Never 的意义:训练是"跑完即结束"的任务型负载,不是 nginx 那种要长期运行的服务。默认 K8s 会把容器退出当成"出问题"而重启,任务型负载要设 Never。
bash
kubectl apply -f train-pod.yaml
kubectl get pods -w # 状态流转:ContainerCreating → Running → Completed
kubectl logs pytorch-train # 看 loss 下降
状态对比 :训练任务正常跑完是 Completed(任务型负载做完);nginx 那种服务是一直 Running。
成功输出示例(loss 一路下降):
训练开始
Epoch 5 | Loss: 1.0318
...
Epoch 50 | Loss: 0.9871
训练完成
(NumPy 的 UserWarning 无害,CPU 版 torch 没带 numpy 而已,代码没用到。)
5. 新踩的坑 + 排查直觉
坑 1:集群重启后变 Stopped
电脑/Docker Desktop 重启后,minikube status 全部 Stopped,kubectl 报 connection refused(连不上 apiserver)。
- 原因:minikube 是开发用本地集群,不会开机自启,电脑重启后集群停了。
- 解决:
minikube start --driver=docker --base-image="gcr.io/k8s-minikube/kicbase:v0.0.50",数据都还在,比首次快很多。 - 排查直觉 :回来发现 kubectl 报 connection refused → 第一反应
minikube status看集群是否停了。 - 衍生现象:集群停着时
minikube image load不报错但不生效,minikube image ls空白。集群恢复后需重新 load。
坑 2:改了代码但容器还跑旧代码
改 train.py 后直接重跑 Pod,结果还是旧行为/旧报错。
-
原因:Pod 用的是镜像里打包的代码快照,不是磁盘上的 train.py。镜像是构建时的固化快照。
-
解决(容器化开发的标准循环):
bash# 1. 改 train.py docker build -t pytorch-train:v2 . # 重新构建(用新 tag 做版本管理;分层缓存让这步很快) minikube image load pytorch-train:v2 # 重新 load kubectl delete pod pytorch-train # 删旧 Pod # 改 YAML 里 image 为 :v2 kubectl apply -f train-pod.yaml # 重新跑 -
核心认知:改代码 → 重新 build → 重新 load → 重新跑。这是容器化开发的固定节奏,会重复无数次。
坑 3:Pod 状态 Error(容器跑了但程序报错)
本例是 train.py 拼写错 nn.Squential → 应为 nn.Sequential。PyTorch 报错很友好,直接提示 Did you mean: 'Sequential'?。
- 解决:看
kubectl logs <pod>,错误堆栈直接指出问题行。
关键排查分流(重要直觉)
| Pod 状态 | 含义 | 去哪看 |
|---|---|---|
| Pending / ImagePullBackOff / ErrImagePull | K8s 层面没把容器跑起来(调度、拉镜像) | kubectl describe pod 看 Events |
| Error / CrashLoopBackOff | 容器跑起来了,但里面程序自己出错 | kubectl logs 看程序输出 |
6. 阶段一毕业 --- 已掌握能力总览
模块一 Linux :命令行、管道组合思维、grep/awk/sort、进程管理、shell 脚本(gpu_monitor.sh)
模块二 Docker :镜像vs容器、隔离、Dockerfile、分层缓存、构建镜像、容器里跑通 nvidia-smi(GPU 链路)
模块三 K8s:声明式、Pod/Deployment、自愈、扩缩容、image load、集群里跑通 PyTorch 训练
最值钱的资产 = 亲手填过的坑与排查直觉 :
docker 权限、镜像源 EOF、二进制下成错误页(file 验证习惯)、kicbase 指纹、ImagePullBackOff、集群重启 Stopped、改码要重构建、Error 看 logs / Pending 看 describe。
这些是 Infra 工程师的日常,也是面试可讲的真实故事。
核心认知:"声明 Pod → 调度 → 拉容器 → 跑训练 → 看 loss"这套链路,换成几百张 GPU、换成大模型,结构完全一样。