【infra之路】模块三:Kubernetes (下) — 阶段一毕业项目:在集群里跑 PyTorch 训练

AI Infra 学习路线 · 阶段一 · 模块三(下半部分)= 阶段一毕业项目

目标:把训练代码打包成镜像 → load 进集群 → 写 Pod 跑训练 → kubectl logs 看 loss 下降

环境:Windows + WSL2 + Docker Desktop + minikube + PyTorch (CPU 版)


0. 关于 GPU 透传:为什么这步用 CPU 而非 GPU(重要决策记录)

原计划是在 minikube 里跑 GPU 训练,但经过调研后决定暂缓 GPU 透传,改用 CPU 跑通整条编排链路。原因:

  1. 多层透传太脆弱:GPU 要穿透 物理卡 → WSL2(靠 /dev/dxg 桥接)→ Docker Desktop → minikube 节点容器 → 训练 Pod。模块二已打通前三层,但后两层在 WSL+Docker 驱动下官方支持时好时坏。
  2. device plugin 缺 NVML 库:WSL2 下把 GPU 支持延伸到 K8s(GPU Operator / device plugin)需要额外配置,常见错误是 plugin 容器找不到 NVML 库,需特殊处理才能让底层 GPU 库在容器里可见。
  3. Blackwell 架构(RTX 5060 Ti)在 WSL 下有已知 CUDA 兼容问题:出现过 nvidia-smi 正常但 CUDA 对应用不可用的情况。即便透传成功也可能在 PyTorch 调 CUDA 这最后一关失败。
  4. 社区经验 :踩通这条路的人最后大多建议绕开 Docker Desktop,改用原生 Docker(WSL 装 systemd + 原生 Docker + nvidia-ctk)或原生 Ubuntu。

结论 :用 CPU 跑通"K8s 编排训练任务"的完整链路(核心可迁移技能),GPU 调度的 YAML 写法用讲解补充。真正的 GPU 训练等将来上云租多卡机器、或切换到干净的原生环境时再做------那时环境干净反而顺。CPU 训练和 GPU 训练在 K8s 里的编排结构完全一样,只是底层算力不同。

GPU 调度的 Pod 写法(供将来参考):在容器 spec 里加

yaml 复制代码
resources:
  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、换成大模型,结构完全一样。

相关推荐
Waay1 小时前
K8s新手实操|emptyDir卷超详细实战(附完整命令+核心理解)
云原生·容器·kubernetes
liux35282 小时前
K8s 核心接口:CNI、CSI、CRI、LB 一篇讲透
云原生·容器·kubernetes
不羁的木木2 小时前
《HarmonyOS技术精讲》三:记忆链接 ── 跨场景数据融合
pytorch·华为·harmonyos
香气袭人知骤暖2 小时前
人大金仓(KingbaseES)Docker 容器自动备份方案
运维·docker·容器
Devin~Y2 小时前
从内容社区到AIGC客服:Spring Boot、Redis、Kafka、K8s、RAG的三轮大厂Java面试对话(附标准答案)
java·spring boot·redis·spring cloud·kafka·kubernetes·micrometer
大熊程序猿3 小时前
MarkItDown Docker安装
运维·docker·容器
zhangfeng11333 小时前
国家超算中心 系统自带模型 和pytorch 和cuda版本
人工智能·pytorch·python
IT策士3 小时前
第25篇 k8s之Deployment 基础:声明式管理与副本控制
云原生·容器·kubernetes
IT策士3 小时前
第 26 篇 k8s之Deployment 进阶:滚动更新、回滚与暂停
云原生·容器·kubernetes