
Author: xidianwangtao@gmail.com
截止Kubernetes 1.8版本:
目前,Kubernetes主要负责GPU资源的检测和调度,真正跟NVIDIA Driver通信的还是docker,因此整个逻辑结构图如下:

nvidia-docker-plugin来确认是否已加载Drivers。 kubectl get node $GPU_Node_Name -o yaml查看该Node的信息,如果看到.status.capacity.alpha.kubernetes.io/nvidia-gpu: $Gpu_num,则说明kubelet已经成功通过driver识别到了本地的GPU资源。kube-apiserver, kube-controller-manager, kube-scheduler, kubelet, kube-proxy每个组件的--feature-gatesflag中都包含Accelerators=true(虽然实际上不是每个组件都需要配置这一项,比如kube-proxy)注意在BIOS里面检查你的UEFI是否开启,如果开启的话请立马关掉它,否则nvidia驱动可能会安装失败。
如果你使用的是Kubernetes 1.8,那么也可以利用kubernetes device plugin这一Alpha特性,让第三方device plugin发现和上报资源信息给kubelet,Nividia有对应的plugin,请参考nvidia k8s-device-plugin。nvidia k8s-device-plugin通过DaemonSet方式部署到GPUs Server中,下面是其yaml描述文件内容:
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: nvidia-device-plugin-daemonset
spec:
template:
metadata:
labels:
name: nvidia-device-plugin-ds
spec:
containers:
- image: nvidia-device-plugin:1.0.0
name: nvidia-device-plugin-ctr
imagePullPolicy: Never
env:
- name: NVIDIA_VISIBLE_DEVICES
value: ALL
- name: NVIDIA_DRIVER_CAPABILITIES
value: utility,compute
volumeMounts:
- name: device-plugin
mountPath: /var/lib/kubelet/device-plugins
volumes:
- name: device-plugin
hostPath:
path: /var/lib/kubelet/device-plugins关于Kubernetes Device Plugin,后面有机会我再单独写一篇博文来深入分析。
不同于cpu和memory,你必须强制显式申明你打算使用的GPU number,通过在container的resources.limits中设置alpha.kubernetes.io/nvidia-gpu为你想要使用的GPU数,通过设置为1就已经足够了,应该没多少训练场景一个worker需要独占几块GPU的。
kind: Pod
apiVersion: v1
metadata:
name: gpu-pod
spec:
containers:
- name: gpu-container-1
image: gcr.io/google_containers/pause:2.0
resources:
limits:
alpha.kubernetes.io/nvidia-gpu: 1
volumeMounts:
- mountPath: /usr/local/nvidia
name: nvidia
volumes:
- hostPath:
path: /var/lib/nvidia-docker/volumes/nvidia_driver/384.98
name: nvidia注意,需要将主机上的nvidia_driver通过hostpath挂载到容器内的/usr/local/nvidia
有些同学或许已经有疑问了:为啥没看到设置resources.requests,直接设置resources.limits?
熟悉Kubernetes中LimitRanger和Resource QoS的同学应该就发现了,这种对GPU resources的设置是属于QoS为Guaranteed,也就是说:
limits,不设置requests,那么requests其实就等于limits。limits和requests,但两者必须值相等。requests,而不设置limits,这种情况属于Burstable。注意,在Kubernetes 1.8.0 Release版本中,存在一个bug:设置GPU requests小于limits是允许的,具体issue可以参考Issue 1450,代码已经合并到v1.8.0-alpha.3中,请使用时注意。下面是对应的修改代码。
pkg/api/v1/validation/validation.go
func ValidateResourceRequirements(requirements *v1.ResourceRequirements, fldPath *field.Path) field.ErrorList {
...
// Check that request <= limit.
limitQuantity, exists := requirements.Limits[resourceName]
if exists {
// For GPUs, not only requests can't exceed limits, they also can't be lower, i.e. must be equal.
if quantity.Cmp(limitQuantity) != 0 && !v1helper.IsOvercommitAllowed(resourceName) {
allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit", resourceName)))
} else if quantity.Cmp(limitQuantity) > 0 {
allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit", resourceName)))
}
} else if resourceName == v1.ResourceNvidiaGPU {
allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s request", v1.ResourceNvidiaGPU)))
}
}
return allErrs
}关于Kubernetes Resource QoS的更多知识,请参考我的另一篇博文:Kubernetes Resource QoS机制解读。
前面提到,Kubernetes默认不支持GPU硬件的区别和差异化调度,如果你需要这种效果,可以通过NodeAffinity来实现,或者使用NodeSelector来实现(不过,NodeAffinity能实现NodeSelector,并且强大的多,NodeSelector应该很快会Deprecated。)
--node-labels='alpha.kubernetes.io/nvidia-gpu-name=$NVIDIA_GPU_NAME',当然alpha.kubernetes.io/nvidia-gpu-name你可以换成其他你自定义的key,但要注意可读性。这种方式,需要重启kubelet才能生效,属于静态方式。kubectl label node $GPU_Node_Name alpha.kubernetes.io/nvidia-gpu-name=$NVIDIA_GPU_NAME,这是实时生效的,可随时增加删除,属于动态方式。kind: pod
apiVersion: v1
metadata:
annotations:
scheduler.alpha.kubernetes.io/affinity: >
{
"nodeAffinity": {
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms": [
{
"matchExpressions": [
{
"key": "alpha.kubernetes.io/nvidia-gpu-name",
"operator": "In",
"values": ["Tesla K80", "Tesla P100"]
}
]
}
]
}
}
}
spec:
containers:
- name: gpu-container-1
resources:
limits:
alpha.kubernetes.io/nvidia-gpu: 1
volumeMounts:
- mountPath: /usr/local/nvidia
name: nvidia
volumes:
- hostPath:
path: /var/lib/nvidia-docker/volumes/nvidia_driver/384.98
name: nvidia其中Tesla K80, Tesla P100都是NVIDIA GPU的型号。通常,CUDA Libs安装在GPU服务器上,那么使用GPU的Pod可以通过volume type为hostpath的方式使用CUDA Libs。
kind: Pod
apiVersion: v1
metadata:
name: gpu-pod
spec:
containers:
- name: gpu-container-1
image: gcr.io/google_containers/pause:2.0
resources:
limits:
alpha.kubernetes.io/nvidia-gpu: 1
volumeMounts:
- mountPath: /usr/local/nvidia
name: nvidia
volumes:
- hostPath:
path: /var/lib/nvidia-docker/volumes/nvidia_driver/384.98
name: nvidia参考如何落地TensorFlow on Kubernetes将TensorFlow跑在Kubernetes集群中,并且能创建Distributed TensorFlow集群启动训练。

不同的是,在worker对应的Job yaml中按照上面的介绍:
/device:GPU:1, /device:GPU:2, ...进行加速训练了。由于我现在没有闲置的GPU服务器可以用来做实验(都在线上服役中),所以暂时还没有Demo可以展示,还没有采坑的经验可以分享给大家。