Day 8 主线 — AI Infra: GPU + k3s + vLLM + Qwen2.5
完整故事: 跨 WAN 加入主集群失败 → 切独立 k3s → 装 NVIDIA Device Plugin → 部署 vLLM 跑 Qwen2.5-3B-Instruct 结果: 集群外 A800-SXM4-40GB GPU 提供 OpenAI-compatible LLM 推理服务 总耗时: ~3 小时(含 1 小时跨 WAN 调试失败)
0. TL;DR
- A — GPU 节点尝试加入主集群 (跨 WAN): ❌ Cilium VXLAN over WAN 调不通, 切方案
- A — k3s 单节点独立部署: ✅ k3s 1.35 + nvidia-container-toolkit + RuntimeClass nvidia
- B — NVIDIA Device Plugin: ✅ 节点显示
nvidia.com/gpu: 1, nvidia-smi Pod 看到 A800 - C — vLLM + Qwen2.5-3B: ✅ OpenAI-compatible /v1 端点, 推理代码/数学/并发全过
1. 完整事件时间线
| 阶段 | 动作 | 状态 |
|---|---|---|
| 1 | 跨 WAN kubeadm join 主集群 | ✅ Join 成功, ❌ Cilium agent CrashLoop |
| 2 | 加 secondary IP, kubelet --node-ip 公网 | ✅ InternalIP 切换 |
| 3 | Cilium 跨 WAN VXLAN/WireGuard 调不通 | ❌ 放弃方案 |
| 4 | kubeadm reset + 装 k3s | ⏸ SSH 断 30 分钟 |
| 5 | SSH 恢复, k3s active | ✅ k3s 1.35.5 |
| 6 | TLS cert 时间错乱 | ⚠️ 完全 wipe server data 重生成 |
| 7 | CoreDNS 11 min 不 ready (k8s.io plugin issue) | ⏸ 绕过, dnsConfig 用 host DNS |
| 8 | NVIDIA Device Plugin 装 (envvar mode) | ✅ GPU resource 1 |
| 9 | vLLM Qwen2.5-3B 启动 | ✅ ~13min (镜像 + 模型下载) |
| 10 | 推理测试 (中文/代码/数学/并发) | ✅ 全过 |
2. 真坑深度分析(都进 mini-book)
坑 #1 — 跨 WAN K8s 节点 join 是真生产难题
现象: GPU 节点公网 IP + 主集群公网 IP 双向 8472/udp 测通,但 Cilium agent CrashLoopBackOff
Why:
- Cilium 跨 WAN VXLAN 需要双向 UDP 包不丢, NAT 设备保持映射
- WireGuard handshake 也需要 UDP 51871 双向
- Cilium 默认认为节点同网段, 跨 WAN 需要 special 配置 (Cilium ClusterMesh + service mesh discovery)
- 单 GPU 节点加入跨 WAN 主集群,性价比极低 (Pod 调度时只看 GPU 可用, scheduler 不知道节点是远端)
Lesson: GPU 节点应该 独立 k3s 集群 + ArgoCD 多集群联邦 / Cilium ClusterMesh 而不是直接 join
坑 #2 — k3s install 改 iptables 导致 SSH 临时断
现象: curl install.sh | sh 在 SSH 跑到一半,SSH 断,30 分钟才恢复
Why:
- k3s install 启动 k3s server,大幅改 iptables (KUBE-* chains)
- 改的过程中 SSH inbound 短暂中断
- 改完后 supplier NAT/防火墙也需要重新建立连接映射
Fix: 永远在第二通道(VNC/串口)或者 screen/tmux 跑 install 脚本
坑 #3 — k3s TLS cert 时间错乱(节点时钟跳动)
现象:
tls: failed to verify certificate: x509: certificate has expired or is not yet valid
current time 2026-05-26T17:44Z is before 2026-05-26T16:38:13Z
Why:
- k3s install 时节点时间可能短暂错误 (UTC 偏差 8h),cert 生成 NotBefore 写成未来 8h
- 节点 NTP 同步后时间正确,但 cert 已写错
Fix:
systemctl stop k3s
rm -rf /var/lib/rancher/k3s/server # wipe 全部 server state
systemctl start k3s # 重 bootstrap, 新 cert
⚠️ 单纯删 tls/ 目录不够 — k3s 的 bootstrap data 也存在 sqlite (db/state.db),要全 wipe
坑 #4 — k3s 自带 CoreDNS 长期 NotReady
现象: k3s 装好后,CoreDNS Pod Running 但 0/1, readiness probe 返回 503,15+ 分钟不恢复
CoreDNS log: Plugins not ready: "kubernetes"
Why: CoreDNS 用 in-cluster Kubernetes plugin → 通过 service kubernetes:443 (10.43.0.1) 连 apiserver → service routing iptables 没及时 setup
Fix (不修 CoreDNS,绕过):
spec:
template:
spec:
dnsPolicy: None
dnsConfig:
nameservers: [223.5.5.5, 8.8.8.8] # ← 直接用阿里 + Google DNS
业务 Pod 不依赖 cluster DNS,直接解析公网 host。
坑 #5 — NVIDIA Device Plugin CDI 模式 vs envvar 模式
默认 v0.17.0 用 CDI mode (Container Device Interface),但需要 nvidia-container-toolkit + containerd 双方 CDI 一致, 我们装时没启用 CDI:
identifier is not a valid UUID or index: "/var/run/nvidia-container-devices"
Fix: Patch 改 envvar mode (老兼容):
env:
- {name: DEVICE_LIST_STRATEGY, value: envvar}
- {name: PASS_DEVICE_SPECS, value: "false"}
- {name: DEVICE_ID_STRATEGY, value: uuid}
坑 #6 — supplier NodePort 没开公网
现象: GPU 节点本机 curl localhost:30800 OK, 主集群 cross-WAN curl ***.109.239.32:30800 Connection refused
Why: GPU 算力供应商默认只 NAT SSH 端口 (15128 → 22),其他端口要后台开
对策:
- 后台手动开端口
- 或者用 SSH tunnel:
ssh -L 30800:localhost:30800 gpu1 - 或者用 cloudflare tunnel / ngrok
3. vLLM Qwen2.5-3B 完整能力验证
3.1 部署清单(Day 8.C 最终版)
apiVersion: apps/v1
kind: Deployment
metadata: {name: vllm-qwen, namespace: vllm}
spec:
replicas: 1
strategy: {type: Recreate} # GPU 不能 share, RollingUpdate 会导致新旧 Pod 抢
selector: {matchLabels: {app: vllm-qwen}}
template:
metadata: {labels: {app: vllm-qwen}}
spec:
runtimeClassName: nvidia # ← 关键: 用 nvidia container runtime
dnsPolicy: None
dnsConfig:
nameservers: ["223.5.5.5", "8.8.8.8"]
containers:
- name: vllm
image: docker.m.daocloud.io/vllm/vllm-openai:v0.6.5
args:
- "--model"
- "Qwen/Qwen2.5-3B-Instruct"
- "--host"
- "0.0.0.0"
- "--port"
- "8000"
- "--gpu-memory-utilization"
- "0.85"
- "--max-model-len"
- "4096"
- "--served-model-name"
- "qwen2.5-3b"
env:
- {name: HF_ENDPOINT, value: "https://hf-mirror.com"} # 国内 HF mirror
ports: [{containerPort: 8000}]
resources:
limits: {nvidia.com/gpu: 1}
volumeMounts:
- {name: hf-cache, mountPath: /root/.cache/huggingface}
- {name: dshm, mountPath: /dev/shm} # vLLM 内部用共享内存
readinessProbe:
httpGet: {path: /health, port: 8000}
initialDelaySeconds: 60
periodSeconds: 10
failureThreshold: 200 # 加载模型时间长, threshold 大
volumes:
- {name: hf-cache, hostPath: {path: /opt/hf-cache, type: DirectoryOrCreate}}
- {name: dshm, emptyDir: {medium: Memory, sizeLimit: 8Gi}}
3.2 启动时间分解
| 阶段 | 耗时 |
|---|---|
| Pod schedule | 5s |
| 拉 vllm/vllm-openai:v0.6.5 image (~10GB) | ~7min |
| 拉 Qwen2.5-3B 模型 (~6GB from hf-mirror) | ~5min |
| vLLM 加载到 GPU + 编译 CUDA kernel | ~1min |
| 总 cold start | ~13min |
3.3 推理性能(单卡 A800-40G)
测试 1 — 中文问答:
Q: 用一句话介绍下 Kubernetes
A: Kubernetes 是一个开源的容器编排系统,用于自动化应用的部署、扩展和管理。
prompt: 34 tokens, completion: 22 tokens, total: 56 tokens
测试 2 — 代码生成:
Q: 用 Go 写一个简单的 HTTP server,只返回 hello world
A: (完整 Go 代码 + 文件结构 + 注释解释)
完整 Go HTTP server with helloWorldHandler, ListenAndServe :8080
prompt: 47, completion: 300 (max), total: 347
测试 3 — 数学推理:
Q: 125 * 36 = ? 用心算给出答案 + 步骤
A: 125 * 36 = 125 * (30 + 6) = 125*30 + 125*6 = 3750 + 750 = 4500
分步给出, 用 LaTeX 包装数学
测试 4 — 并发吞吐:
for i in 1..5; do curl /v1/completions ... &; done
wait
- 5 并发请求, GPU util 从 36% → 85%
- 每个请求 86 tokens
- 总 430 tokens, 都在 5s 内完成
- GPU 显存稳定 33.7G / 40.9G (82%)
3.4 GPU 资源占用分析
| 项 | 数值 |
|---|---|
| GPU 型号 | NVIDIA A800-SXM4-40GB |
| 总显存 | 40960 MiB |
vLLM 占用 (configured gpu-memory-utilization=0.85) | ~34.8 GB (target) |
| 实测占用 | 33.7 GB (空闲 6.7 GB) |
| 推理时 GPU util | 36% (单请求) → 85% (5 并发) |
| 模型本身大小 (FP16) | 6.2 GB |
| KV cache + activation | ~28 GB (动态) |
注: max_model_len=4096 时 KV cache 上限,可挂更长上下文但要看 batch size
4. 架构图(最终形态)
┌─────────────────────────────────────────────────────────────────────┐
│ 主集群 (5 节点, 内网 10.0.24.0/24) │
│ ├─ Cilium WireGuard / Hubble / Prometheus / Loki │
│ ├─ Longhorn (634G storage) / Harbor / ArgoCD / Jenkins / Gitea │
│ ├─ AlertManager → webhook-mock (Day 8 alt) │
│ └─ Kyverno (Audit mode) │
│ │
│ ↓ 公网 (NAT 转发 + SSH tunnel) │
│ │
│ GPU 节点 (k3s 单节点, 公网 ***.109.239.32) │
│ ├─ k3s 1.35 (无 kube-proxy, embed flannel) │
│ ├─ nvidia-container-toolkit + RuntimeClass nvidia │
│ ├─ NVIDIA Device Plugin DS (envvar mode) │
│ └─ vLLM Deployment + NodePort :30800 │
│ └─ Qwen2.5-3B-Instruct on A800-40G GPU │
│ /v1/chat/completions /v1/models /health │
└─────────────────────────────────────────────────────────────────────┘
5. 简历可写
跨地域 K8s 部署 LLM 推理服务:
- GPU 节点 (A800-SXM4-40GB) 独立 k3s 集群, NVIDIA Container Toolkit + Device Plugin (envvar mode 解决 CDI 兼容性)
- vLLM 0.6.5 部署 Qwen2.5-3B-Instruct,OpenAI-compatible /v1 端点,gpu-memory-utilization 0.85 (33.7G 占用,达 80%+ util 并发推理)
- 主集群通过 NodePort + SSH tunnel 跨 WAN 调用 GPU 服务
- 完整解决: k3s TLS cert 时间漂移 / CoreDNS NotReady / NVIDIA CDI 不兼容 / supplier NAT 端口配置 — 4 个真生产问题
6. 后续(Day 9 候选)
完成 vLLM 推理 demo 后,Day 9 可做:
- Triton Inference Server — NVIDIA 官方推理服务器,多模型并发,gRPC + HTTP
- Ray Serve — 分布式推理,scale-up + scale-out
- ONNX Runtime — 跨框架模型推理 (PyTorch / TF / ONNX 转换)
- vLLM 上 70B 模型 — A800-40G 可以跑 14B Q8 量化, 或 70B Q4 量化(需要 tensor-parallel 切但单卡)
- 业务接入 demo — 用 Day 8 番外的 Jenkins/ArgoCD 部署 chat UI,调 vLLM API
99. Day 8 主线 完成
- [x] A — GPU 节点准备 + k3s 单节点(踩 5 个坑)
- [x] B — NVIDIA Device Plugin + RuntimeClass nvidia
- [x] C — vLLM Qwen2.5-3B 推理 (代码/数学/并发全过)