title: “GPU on Kubernetes 落地:RKE2、NVIDIA Runtime、Multus 与可登录训练 Pod”
tags:
- Kubernetes
- RKE2
- GPU
- NVIDIA
- Multus
- Whereabouts
- MLOps
GPU on Kubernetes 落地:RKE2、NVIDIA Runtime、Multus 与可登录训练 Pod
这篇文章解决一个很常见、也很容易被低估的问题:
机器上 nvidia-smi 明明能看到 GPU,为什么 Kubernetes 还是调度不了 GPU Pod?
答案通常不在“显卡有没有插好”这一层,而在这条链路里:
Host NVIDIA Driver
-> NVIDIA Container Toolkit
-> containerd 的 nvidia runtime handler
-> Kubernetes RuntimeClass
-> NVIDIA device plugin 初始化 NVML
-> kubelet 上报 nvidia.com/gpu
-> 业务 Pod request/limit nvidia.com/gpu
只要其中一环断开,Kubernetes 调度器就可能“看不见” GPU。
本文基于 RKE2 + containerd + NVIDIA Container Toolkit + NVIDIA device plugin + Multus + Whereabouts 的组合,整理一套可复制的 GPU on K8s 落地方案。
1. 方案目标
本次目标不是单纯跑通一个 nvidia-smi 测试 Pod,而是要交付一个可用于训练、调试和后续扩展的基础形态:
- GPU 节点加入 RKE2 集群。
- Kubernetes 能正确识别并调度
nvidia.com/gpu。 - GPU Pod 显式使用 NVIDIA RuntimeClass。
- 提供一个可 SSH 登录的 GPU 训练 Pod,便于交互式调试。
- 保留默认 Pod 网络,同时通过 Multus 添加二层网卡。
- 支持固定 IP 或地址池自动分配。
- 支持后续扩展为管理网、算力网多网卡模型。
- 明确容器模型下“系统盘”和“数据盘”的边界。
公开博客里的示例采用这些占位符:
| 项目 | 示例 |
|---|---|
| GPU 节点 | <gpu-worker-01> |
| GPU 节点内网 IP | <node-ip> |
| GPU 节点主机二层网卡 | <master-nic>,例如 enp5s0 或 bond0 |
| 管理二层网段 | 10.20.30.0/24 |
| Pod 固定二层 IP | 10.20.30.240/24 |
| Whereabouts 地址池 | 10.20.30.241-10.20.30.250/24 |
| SSH NodePort | 30022,仅作示例 |
| 镜像仓库 | 按你的环境替换为公网仓库或内网镜像仓库 |
2. 为什么“节点有 GPU”不等于“K8s 能调度 GPU”
很多 GPU 接入问题都卡在一个认知误区:
nvidia-smi在宿主机能跑通,Kubernetes 就应该自动知道这台机器有 GPU。
实际上不是。
Kubernetes 不会天然扫描宿主机上的所有 GPU 并把它们变成可调度资源。它依赖 device plugin 框架把厂商设备注册给 kubelet。
NVIDIA GPU 的常见资源名是:
nvidia.com/gpu
只有当 NVIDIA device plugin 在节点上成功启动、成功初始化 NVML、成功向 kubelet 注册后,节点状态里才会出现类似:
status:
capacity:
nvidia.com/gpu: "1"
allocatable:
nvidia.com/gpu: "1"
如果这里没有 nvidia.com/gpu,调度器就不会把这个节点当作 GPU 节点。
3. 本次问题的典型现象
落地过程中看到的典型现象是:
- GPU 节点已经打了标签,例如
nvidia.com/gpu.present=true。 - 宿主机执行
nvidia-smi正常。 - 业务 Pod 申请了
nvidia.com/gpu: 1后一直 Pending。 kubectl describe node看不到nvidia.com/gpucapacity。- NVIDIA device plugin 日志出现类似错误:
Failed to initialize NVML: could not load NVML library.
这类错误的关键不是“device plugin 本身坏了”,而是它的容器没有通过 NVIDIA runtime 启动。
device plugin 容器如果走默认 runc,容器内可能拿不到 NVIDIA 驱动库和 NVML 能力。它就无法发现 GPU,也无法向 kubelet 注册 nvidia.com/gpu。
最终表现就是:节点上有卡,但 Kubernetes 看不到卡。
4. 节点侧基线:驱动、Toolkit、containerd
GPU 节点首先要满足宿主机层面的条件:
- NVIDIA 驱动安装完成。
- 宿主机
nvidia-smi正常。 - NVIDIA Container Toolkit 安装完成。
- containerd 已配置
nvidiaruntime handler。 - RKE2 agent 重启后加载 containerd 配置。
Ubuntu/Debian 系节点安装 Toolkit 的官方流程大致是:
sudo apt-get update
sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg2
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
| sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
| sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \
| sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
然后配置 containerd:
sudo nvidia-ctk runtime configure --runtime=containerd
普通 containerd 环境通常会生成或修改:
/etc/containerd/conf.d/99-nvidia.toml
/etc/containerd/config.toml
但 RKE2 使用的是内置 containerd。实际配置路径通常在:
/var/lib/rancher/rke2/agent/etc/containerd/config.toml
所以在 RKE2 场景里要特别确认:RKE2 最终生成的 containerd 配置里确实包含 nvidia runtime handler,并且 handler 指向 nvidia-container-runtime。
可在 GPU 节点上检查:
sudo grep -n "nvidia" /var/lib/rancher/rke2/agent/etc/containerd/config.toml || true
which nvidia-container-runtime
nvidia-smi
如果修改过 runtime 配置,重启 RKE2 agent:
sudo systemctl restart rke2-agent
5. RuntimeClass:把 nvidia runtime 暴露给 Pod
Kubernetes 的 RuntimeClass 用来让 Pod 选择不同的容器运行时配置。
如果你没有把 NVIDIA runtime 设置成节点默认 runtime,就应该显式创建一个 RuntimeClass/nvidia:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: nvidia
handler: nvidia
验证:
kubectl get runtimeclass
kubectl get runtimeclass nvidia -o yaml
业务 Pod 使用时写:
spec:
runtimeClassName: nvidia
这一步非常关键。
仅仅申请 nvidia.com/gpu: 1,并不必然代表容器会用 NVIDIA runtime 启动。资源分配和运行时选择是两件事。
在本文方案里,我们让两类 Pod 都显式使用 runtimeClassName: nvidia:
- NVIDIA device plugin DaemonSet。
- 实际使用 GPU 的业务 Pod。
6. 修复关键:device plugin 自己也要走 NVIDIA runtime
很多人只关注业务 Pod 的 runtimeClassName,但这次真正卡住的是 device plugin 自己。
device plugin 的职责是发现 GPU、监控 GPU 健康状态、向 kubelet 注册资源。
如果 device plugin 这个容器没有通过 NVIDIA runtime 启动,它可能连 NVML 都加载不到。于是 kubelet 就收不到 GPU 注册信息。
下面是一个脱敏后的 DaemonSet 示例。
生产环境建议通过 Helm 或 GitOps 固定版本。示例中的镜像请按你的环境替换为官方镜像、企业镜像仓库或可信镜像代理。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nvidia-device-plugin-daemonset
namespace: kube-system
spec:
selector:
matchLabels:
app: nvidia-device-plugin-ds
template:
metadata:
labels:
app: nvidia-device-plugin-ds
spec:
runtimeClassName: nvidia
priorityClassName: system-node-critical
nodeSelector:
nvidia.com/gpu.present: "true"
tolerations:
- key: CriticalAddonsOnly
operator: Exists
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
containers:
- name: nvidia-device-plugin-ctr
image: nvcr.io/nvidia/k8s-device-plugin:<固定版本>
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: device-plugin
mountPath: /var/lib/kubelet/device-plugins
volumes:
- name: device-plugin
hostPath:
path: /var/lib/kubelet/device-plugins
几个点要讲清楚:
runtimeClassName: nvidia:让 plugin 容器能访问 NVIDIA runtime 注入的能力。nodeSelector:只跑在标记过的 GPU 节点上。priorityClassName:把它作为关键节点组件处理。/var/lib/kubelet/device-plugins:device plugin 和 kubelet 通过这个目录下的 socket 通信。
给 GPU 节点打标签:
kubectl label node <gpu-worker-01> nvidia.com/gpu.present=true
应用并等待:
kubectl apply -f nvidia-plugin.yaml
kubectl -n kube-system rollout status ds/nvidia-device-plugin-daemonset --timeout=600s
7. GPU 注册验证
先看 device plugin Pod:
kubectl -n kube-system get pod -o wide | grep nvidia-device-plugin
kubectl -n kube-system logs -l app=nvidia-device-plugin-ds --tail=200
期望看到类似:
Loading NVML
Registered device plugin with Kubelet
再看节点资源:
kubectl get node <gpu-worker-01> \
-o jsonpath='capacity={.status.capacity.nvidia\.com/gpu} allocatable={.status.allocatable.nvidia\.com/gpu}{"\n"}'
期望:
capacity=1 allocatable=1
如果这里仍为空,排查顺序建议是:
- 宿主机
nvidia-smi是否正常。 - RKE2 containerd 配置里是否有
nvidiaruntime。 RuntimeClass/nvidia是否存在,handler 是否为nvidia。- device plugin Pod 是否真的使用了
runtimeClassName: nvidia。 - device plugin 日志是否还有 NVML 错误。
8. 最小 GPU 测试 Pod
节点上报 GPU 后,用一个最小 Pod 验证调度链路:
apiVersion: v1
kind: Pod
metadata:
name: gpu-smoke-test
spec:
restartPolicy: Never
runtimeClassName: nvidia
nodeSelector:
kubernetes.io/hostname: <gpu-worker-01>
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
containers:
- name: cuda
image: nvidia/cuda:12.4.1-base-ubuntu22.04
command: ["bash", "-lc", "nvidia-smi && echo GPU_OK"]
resources:
limits:
nvidia.com/gpu: 1
执行:
kubectl apply -f gpu-smoke-test.yaml
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/gpu-smoke-test --timeout=300s
kubectl logs gpu-smoke-test
kubectl delete pod gpu-smoke-test
日志里看到 nvidia-smi 输出和 GPU_OK,说明 GPU 从节点注册到业务容器访问这条链路已经跑通。
9. 可 SSH 登录的 GPU 训练 Pod
很多训练场景早期需要交互式调试。最直接的交付形态是一个 StatefulSet:
runtimeClassName: nvidia。- 申请
nvidia.com/gpu: 1。 - 用 Headless Service 保留稳定 DNS。
- 用 NodePort 或后续 LoadBalancer 暴露 SSH。
- 用 Secret 注入 SSH 公钥或临时密码。
- 通过 startupProbe 避免首次安装工具时被 kubelet 误杀。
正式生产不建议直接 root + 密码 + NodePort。下面示例使用 SSH key,并禁用密码登录。
先创建 SSH 公钥 Secret:
kubectl create namespace gpu-train
kubectl -n gpu-train create secret generic gpu-ssh-authorized-keys \
--from-file=authorized_keys=./authorized_keys
StatefulSet 示例:
apiVersion: v1
kind: Service
metadata:
name: gpu-train-headless
namespace: gpu-train
spec:
clusterIP: None
selector:
app: gpu-train
ports:
- name: ssh
port: 22
targetPort: 22
---
apiVersion: v1
kind: Service
metadata:
name: gpu-train-ssh
namespace: gpu-train
spec:
type: NodePort
selector:
app: gpu-train
ports:
- name: ssh
port: 22
targetPort: 22
nodePort: 30022
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: gpu-train
namespace: gpu-train
spec:
serviceName: gpu-train-headless
replicas: 1
selector:
matchLabels:
app: gpu-train
template:
metadata:
labels:
app: gpu-train
spec:
runtimeClassName: nvidia
nodeSelector:
kubernetes.io/hostname: <gpu-worker-01>
terminationGracePeriodSeconds: 10
containers:
- name: main
image: nvidia/cuda:12.4.1-base-ubuntu22.04
imagePullPolicy: IfNotPresent
command: ["bash", "-lc"]
args:
- |
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get -o Acquire::ForceIPv4=true -o Acquire::Retries=5 update
apt-get -o Acquire::ForceIPv4=true -o Acquire::Retries=5 install -y --no-install-recommends \
ca-certificates \
openssh-server \
iproute2 \
net-tools \
iputils-ping \
curl \
wget \
git \
vim-tiny \
less \
procps
mkdir -p /run/sshd /root/.ssh
cp /ssh/authorized_keys /root/.ssh/authorized_keys
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
sed -ri 's/^#?PermitRootLogin[[:space:]]+.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
sed -ri 's/^#?PasswordAuthentication[[:space:]]+.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -ri 's/^#?PubkeyAuthentication[[:space:]]+.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
exec /usr/sbin/sshd -D -e
volumeMounts:
- name: ssh-authorized-keys
mountPath: /ssh
readOnly: true
ports:
- name: ssh
containerPort: 22
startupProbe:
tcpSocket:
port: 22
periodSeconds: 5
failureThreshold: 360
readinessProbe:
tcpSocket:
port: 22
periodSeconds: 5
livenessProbe:
tcpSocket:
port: 22
periodSeconds: 10
resources:
limits:
nvidia.com/gpu: 1
volumes:
- name: ssh-authorized-keys
secret:
secretName: gpu-ssh-authorized-keys
部署并验证:
kubectl apply -f gpu-ssh.yaml
kubectl -n gpu-train rollout status statefulset/gpu-train --timeout=1200s
kubectl -n gpu-train get pod -o wide
kubectl -n gpu-train logs -f gpu-train-0
登录:
ssh root@<node-ip> -p 30022
进入容器后验证:
nvidia-smi -L
ip -br a
这个形态适合早期调试和小规模训练环境。规模化训练平台建议进一步封装为镜像、模板、队列和租户权限模型。
10. 为什么需要 Multus:默认 Pod IP 不适合所有训练流量
Kubernetes 默认只给 Pod 创建一张网卡,通常叫 eth0。
这张网卡由主 CNI 负责,例如 Cilium、Calico、Flannel 等。它适合服务发现、集群内通信和普通业务访问。
但 GPU 训练场景常常还有额外需求:
- Pod 需要一个和物理网络同网段的固定 IP。
- 运维人员希望直接 SSH 到训练环境。
- 分布式训练希望流量走独立算力网卡。
- RoCE、IB 等网络可能需要绕过默认 Overlay。
- 多租户场景希望管理网、数据网、训练网分离。
这时可以用 Multus 给 Pod 追加第二张、第三张网卡。
本文的网络模型是:
eth0: 主 CNI 创建,保留 Kubernetes 默认 Pod 网络
net1: Multus 创建,ipvlan L2,接入管理二层网络
net2: 可选,Multus 创建,接入算力网络
11. RKE2 安装 Multus + Whereabouts
RKE2 官方支持通过内置 HelmChart 方式安装 Multus,并可启用 rke2-whereabouts。
示例:
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
name: rke2-multus
namespace: kube-system
spec:
repo: https://rke2-charts.rancher.io
chart: rke2-multus
targetNamespace: kube-system
valuesContent: |
rke2-whereabouts:
enabled: true
应用:
kubectl apply -f rke2-multus.yaml
kubectl -n kube-system rollout status ds/rke2-multus --timeout=600s
kubectl -n kube-system rollout status ds/rke2-multus-rke2-whereabouts --timeout=600s
验证 NAD CRD:
kubectl get crd network-attachment-definitions.k8s.cni.cncf.io
验证 CNI 二进制:
ls -l /opt/cni/bin | egrep 'multus|whereabouts|ipvlan|macvlan|static|host-local' || true
注意一个容易踩的点:
rke2-whereabouts DaemonSet 起来,不代表 Whereabouts 所需的所有 CRD 一定已存在。使用 ipam.type: whereabouts 前,应确认:
kubectl get crd | grep whereabouts.cni.cncf.io
至少应看到类似:
ippools.whereabouts.cni.cncf.io
overlappingrangeipreservations.whereabouts.cni.cncf.io
如果缺少 CRD,Pod 可能卡在 ContainerCreating,事件里出现找不到 ippools.whereabouts.cni.cncf.io 的错误。
12. 创建 NetworkAttachmentDefinition
NAD 是 Multus 读取的二网卡配置对象。
下面提供两种模式:
ipvlan-static:固定 IP。ipvlan-pool:Whereabouts 从地址池自动分配 IP。
把 <master-nic> 替换成 GPU 节点上真实存在、已经连入对应二层网络的网卡名。
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-static
namespace: gpu-train
spec:
config: |
{
"cniVersion": "0.4.0",
"name": "ipvlan-static",
"type": "ipvlan",
"master": "<master-nic>",
"mode": "l2",
"ipam": {
"type": "static",
"addresses": [
{ "address": "10.20.30.240/24" }
]
}
}
---
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-pool
namespace: gpu-train
spec:
config: |
{
"cniVersion": "0.4.0",
"name": "ipvlan-pool",
"type": "ipvlan",
"master": "<master-nic>",
"mode": "l2",
"ipam": {
"type": "whereabouts",
"range": "10.20.30.241-10.20.30.250/24"
}
}
应用:
kubectl apply -f gpu-train-nads.yaml
kubectl -n gpu-train get net-attach-def
让 Pod 挂载固定 IP 二网卡,在 Pod 模板 annotation 里加入:
metadata:
annotations:
k8s.v1.cni.cncf.io/networks: |
[
{ "name": "ipvlan-static", "namespace": "gpu-train", "interface": "net1" }
]
如果使用地址池:
metadata:
annotations:
k8s.v1.cni.cncf.io/networks: |
[
{ "name": "ipvlan-pool", "namespace": "gpu-train", "interface": "net1" }
]
重建 Pod:
kubectl apply -f gpu-ssh.yaml
kubectl -n gpu-train delete pod gpu-train-0
kubectl -n gpu-train wait --for=condition=Ready pod/gpu-train-0 --timeout=1200s
验证事件:
kubectl -n gpu-train describe pod gpu-train-0 | grep -A3 -E 'AddedInterface|multus'
期望看到类似:
Add net1 [10.20.30.240/24] from gpu-train/ipvlan-static
进入容器:
ip -br a
ip route
同网段机器可以尝试:
ssh root@10.20.30.240
如果你已经通过 net1 固定 IP 登录,NodePort 可以逐步退场,改成更清晰的二层访问模型。
13. net1 state UNKNOWN 不是一定坏了
使用 ipvlan 时,Pod 内可能看到:
net1@if2 UNKNOWN 10.20.30.240/24
这里的 UNKNOWN 是 Linux 接口 operstate,不等价于“网络不可用”。
很多虚拟接口没有物理网卡那样明确的 carrier 状态,所以内核无法准确显示 UP 或 DOWN,会显示 UNKNOWN。
更重要的是看 flags:
ip -d link show net1
如果能看到 UP、LOWER_UP,并且同网段访问正常,就不需要因为 UNKNOWN 误判故障。
ipvlan L2 模式下,Pod 的 net1 复用宿主机 master 网卡收发二层流量。外部机器访问 10.20.30.240 时,流量会经宿主机物理网卡进入,再转到 Pod 网络命名空间。
需要注意:这类二层旁路网卡不一定受主 CNI 的 NetworkPolicy 约束。安全策略应在节点防火墙、上游交换网络、ACL 或独立 VLAN 上补齐。
14. 扩展:管理网与算力网分离
更接近生产的 GPU 训练网络通常不是一张网卡打天下,而是至少分成:
- 管理网:SSH、运维、控制面访问。
- 存储网:数据集、模型权重、检查点读写。
- 算力网:分布式训练通信,例如 NCCL/Gloo。
如果宿主机有第二张物理网卡或 bond,可继续创建一个算力网 NAD:
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-compute-pool
namespace: gpu-train
spec:
config: |
{
"cniVersion": "0.4.0",
"name": "ipvlan-compute-pool",
"type": "ipvlan",
"master": "<compute-nic>",
"mode": "l2",
"ipam": {
"type": "whereabouts",
"range": "192.168.100.10-192.168.100.250/24"
}
}
Pod 同时挂两张二层网卡:
metadata:
annotations:
k8s.v1.cni.cncf.io/networks: |
[
{ "name": "ipvlan-static", "namespace": "gpu-train", "interface": "net1" },
{ "name": "ipvlan-compute-pool", "namespace": "gpu-train", "interface": "net2" }
]
训练框架里显式绑定算力网卡,比改默认路由更稳妥。
NCCL 示例:
export NCCL_SOCKET_IFNAME=net2
Gloo 示例:
export GLOO_SOCKET_IFNAME=net2
不要随意把 Pod 默认路由改到算力网,否则可能影响访问 Kubernetes Service、DNS、镜像仓库和控制面。
15. IP 冲突:二层网络里最难排的坑
Multus + ipvlan/macvlan 把 Pod 接到真实二层网络后,IP 冲突会比普通 Overlay 网络更麻烦。
如果使用 static IPAM,两个 Pod 写了同一个 IP,常见表现是:
- SSH 一会能连,一会超时。
- 同一个 IP 的 ARP 记录来回变化。
- 分布式训练随机断开、卡住、吞吐抖动。
- 故障看起来像训练框架问题,但根因在二层地址冲突。
排查:
ip neigh show | grep '<pod-l2-ip>' || true
arp -an | grep '<pod-l2-ip>' || true
kubectl -n gpu-train describe pod <pod-name> | grep -A5 -E 'multus|whereabouts|Failed'
建议:
- 多副本优先使用 Whereabouts 地址池。
- 必须固定 IP 时,用模板生成每个副本独立 NAD。
- 地址池避开网关、交换机、宿主机、BMC、已有服务器地址。
- 更严谨的生产环境应使用独立 VLAN 或独立物理网络承载训练网。
16. 持久化:容器里的“系统盘”和“数据盘”
有些用户会提出一个很像虚拟机的需求:
我希望训练 Pod 有系统盘和数据盘。重装系统时清空系统盘,但保留数据盘。
在容器模型里,要先把语义讲清楚。
容器的 rootfs 来自镜像,默认是临时写层。你在容器里 apt-get install 到 /usr/bin 的内容,不会因为 StatefulSet 重建而长期稳定保留。
更推荐的做法是:
- 把基础工具、CUDA、SSH、Python、conda 等做进自定义镜像。
- 用 PVC 挂载
/data保存数据集、输出、模型权重。 - 可选再用一个 PVC 挂载
/workspace或/system保存代码、环境产物和用户态工具。
StatefulSet 增加两块盘:
spec:
volumeClaimTemplates:
- metadata:
name: workspace
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 500Gi
template:
spec:
containers:
- name: main
volumeMounts:
- name: workspace
mountPath: /workspace
- name: data
mountPath: /data
StatefulSet 会自动创建类似:
workspace-gpu-train-0
data-gpu-train-0
“重装系统但保留数据盘”的容器语义可以这样做:
kubectl -n gpu-train delete pod gpu-train-0
kubectl -n gpu-train delete pvc workspace-gpu-train-0
# 不删除 data-gpu-train-0
kubectl -n gpu-train get pvc data-gpu-train-0
但如果你真正需要 VM 级别的系统盘、快照、回滚和重装语义,应考虑 KubeVirt、Harvester 或传统虚拟化,而不是把容器强行当 VM 使用。
17. 安全基线
GPU 训练环境常常被临时开放成“能登录就行”,但公开或半公开网络中这样风险很高。
建议至少做到:
- 不把 SSH 密码写进 YAML 。
- 优先使用 SSH key,禁用密码登录。
- NodePort 只允许内网、堡垒机或 VPN 访问。
- 二层
net1/net2所在 VLAN 做 ACL。 - 不把 GPU Pod 暴露到办公网或公网。
- 镜像使用固定 tag 或 digest。
- 用独立 namespace、ResourceQuota 和 LimitRange 控制租户资源。
- 训练数据盘和模型盘配置权限、审计和备份。
- 对可登录 Pod 做生命周期管理,避免长期无人维护。
如果只是临时调试,可以接受 NodePort。
如果是生产平台,应优先走:
用户 -> VPN/堡垒机 -> 平台审计 -> Kubernetes API/Job -> 受控训练环境