前言: 在前面两个章节中已经介绍了如何构建GPU的基础环境以及使用Docker方式来优雅的运行GPU应用,单纯的使用Docker这种方式是无法满足大规模的应用调度和管理的,对于集群调度以及容器化管理方面,我们也采用了业界比较知名的容器编排调度管理工具Kubernetes,本篇文章简单介绍GPU业务容器在Kubernetes上的运行。

GPU环境下玩转Docker(一)

GPU环境下玩转Docker(二)

使用Kubernetes调度GPU容器

1. 准备工作

k8s当前已经支持GPU的资源调度了,详情可查看中文文档:k8s调度GPU。 这里介绍一个简单的实例。

注意:官方网站中也提到,当前k8s调度GPU也还是处于实验性阶段,在测试K8S调度GPU之前需要做以下相关工作。

  • Kubernetes 节点必须预先安装好 NVIDIA 驱动,否则,Kubelet 将检测不到可用的GPU信息;如果节点的 Capacity 属性中没有出现 NIVIDA GPU 的数量,有可能是驱动没有安装或者安装失败,请尝试重新安装
  • 在整个 Kubernetes 系统中,feature-gates 里面特定的 alpha 特性参数 Accelerators 必须设置为 true:–feature-gates=“Accelerators=true”

  • Kuberntes 节点必须使用 docker 引擎作为容器的运行引擎

以上工作完成后,节点会自动发现主机上的NVIDIA GPU机器,并将其作为可调度资源暴露。

注意1:为了防止错误调度,需要给GPU机器去做额外的标签 注意2:kubelet程序在启动时必须加载--allow-privileged=true参数,以对所有的GPU容器设置特权模式来识别宿主机资源

kubelet启动参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# cat  /usr/lib/systemd/system/kubelet.service | grep -v ^#
[Unit]
Description=Kubernetes Kubelet Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=docker.service
Requires=docker.service

[Service]
WorkingDirectory=/export/lib/kubelet
EnvironmentFile=-/export/kubernetes/config
EnvironmentFile=-/export/kubernetes/kubelet
ExecStart=/usr/bin/kubelet \
            $KUBE_LOGTOSTDERR \
            $KUBE_LOG_LEVEL \
            $KUBELET_API_SERVER \
            $KUBELET_ADDRESS \
            $KUBELET_PORT \
            $KUBELET_HOSTNAME \
            $KUBE_ALLOW_PRIV \
            $KUBELET_POD_INFRA_CONTAINER \
            $KUBELET_ARGS \
Restart=on-failure


# cat  /export/kubernetes/kubelet | grep -v ^#
KUBELET_ADDRESS="--address=10.0.0.1"
KUBELET_HOSTNAME="--hostname-override=10.0.0.1"
KUBELET_POD_INFRA_CONTAINER="--pod-infra-container-image=idockerhub.xxb.com/k8s/pause"
KUBELET_ARGS="--cgroup-driver=cgroupfs \
                --cluster-dns=10.254.0.2 \
                --experimental-bootstrap-kubeconfig=/export/kubernetes/bootstrap.kubeconfig \
                --kubeconfig=/export/kubernetes/kubelet.kubeconfig \
                --require-kubeconfig \
                --cert-dir=/export/kubernetes/ssl \
                --cluster-domain=cluster.local. \
                --hairpin-mode promiscuous-bridge \
                --serialize-image-pulls=false \
                --feature-gates='Accelerators=true'"

# cat  /export/kubernetes/config | grep -v ^#
KUBE_LOGTOSTDERR="--logtostderr=true"

KUBE_LOG_LEVEL="--v=0"

KUBE_ALLOW_PRIV="--allow-privileged=true"

KUBE_MASTER="--master=http://10.0.0.2:8080"



#/usr/bin/kubelet --logtostderr=true --v=0 --address=10.0.0.1 --hostname-override=10.0.0.1 --allow-privileged=true --pod-infra-container-image=idockerhub.xxb.com/k8s/pause --cgroup-driver=cgroupfs --cluster-dns=10.254.0.2 --experimental-bootstrap-kubeconfig=/export/kubernetes/bootstrap.kubeconfig --kubeconfig=/export/kubernetes/kubelet.kubeconfig --require-kubeconfig --cert-dir=/export/kubernetes/ssl --cluster-domain=cluster.local. --hairpin-mode promiscuous-bridge --serialize-image-pulls=false --feature-gates=Accelerators=true Restart=on-failure
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# /usr/local/bin/kubectl -s http://10.0.0.2:8080 label nodes 10.0.0.1 type=gpu

# /usr/local/bin/kubectl -s http://10.0.0.2:8080 get nodes  --show-labels
NAME             STATUS    AGE       VERSION   LABELS
10.0.0.2     Ready     2d        v1.6.2    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=10.0.0.2
10.0.0.1   Ready     4h        v1.6.2    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=10.0.0.1,type=gpu


# /usr/local/bin/kubectl -s http://10.0.0.2:8080 get node -l type=gpu
NAME             STATUS    AGE       VERSION
10.0.0.1   Ready     4h        v1.6.2

2. 测试用例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: test-gpu
spec:
  replicas: 1
  template:
    metadata:
      labels:
        name: test-gpu
    spec:
      containers:
      - name: test-gpu
        image: idockerhub.xxb.com/k8s/tensorflow/tensorflow:latest-gpu
        ports:
        - containerPort: 8888
        resources:
          limits:
            alpha.kubernetes.io/nvidia-gpu: 2
        volumeMounts:
            - mountPath: /usr/local/nvidia
              name: nvidia-driver
            - mountPath: /dev/nvidia0
              name: nvidia0
            - mountPath: /dev/nvidia-uvm
              name: nvidia-uvm
            - mountPath: /dev/nvidia-uvm-tools
              name: nvidia-uvm-tools
            - mountPath: /dev/nvidiactl
              name: nvidiactl
      volumes:
        - name: nvidia-driver
          hostPath:
            path: /var/lib/nvidia-docker/volumes/nvidia_driver/375.39
        - name: nvidia0
          hostPath:
            path: /dev/nvidia0
        - name: nvidia-uvm
          hostPath:
            path: /dev/nvidia-uvm
        - name: nvidia-uvm-tools
          hostPath:
            path: /dev/nvidia-uvm-tools
        - name: nvidiactl
          hostPath:
            path: /dev/nvidiactl

注意1:k8s调度gpudocker容器需要和业务方进行强关联,所以对于cuda,cudnn,nvidia-docker的版本要求是非常高的。 注意2:k8s调度的gpu其实是独享的,也就是上面分配了2个gpu设备,那么该宿主机上将最多只能创建2个卡的gpu容器,否则会因为资源不够而调度失败。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ll /var/lib/nvidia-docker/volumes/nvidia_driver/
total 0
drwxr-xr-x 5 nvidia-docker nvidia-docker 38 Oct 18 21:38 375.39


# docker ps
CONTAINER ID        IMAGE                                                                                                                    COMMAND                  CREATED             STATUS              PORTS                                              NAMES
f3a3f8dd30ca        idockerhub.xxb.com/jdjr/tensorflow-gpu@sha256:7844f390a9d5ff369c7756a52ca65dae095087c736935e9310e12c7caa7c73dd            "/run_jupyter.sh --al"   2 days ago          Up 2 days                                                              k8s_test-gpu_test-gpu-1430106381-9n8hb_default_97177dcf-b7da-11e7-b982-ecf4bbc19ea8_0
7650dce5a686        idockerhub.xxb.com/k8s/pause                                                                                              "/pod"                   2 days ago          Up 2 days                                                              k8s_POD_test-gpu-1430106381-9n8hb_default_97177dcf-b7da-11e7-b982-ecf4bbc19ea8_0



# 尝试创建一个4卡的gpu设备的容器
# docker exec -it 0cce1f2a59dd ls -la /dev/nvidia*
crw-rw-rw- 1 root root 244,   0 Jul 19 05:14 /dev/nvidia-uvm
crw-rw-rw- 1 root root 244,   1 Jul 19 05:14 /dev/nvidia-uvm-tools
crw-rw-rw- 1 root root 195,   0 Jun 20 02:54 /dev/nvidia0
crw-rw-rw- 1 root root 195,   1 Oct 23 10:06 /dev/nvidia1
crw-rw-rw- 1 root root 195,   2 Oct 23 10:06 /dev/nvidia2
crw-rw-rw- 1 root root 195,   3 Oct 23 10:06 /dev/nvidia3
crw-rw-rw- 1 root root 195, 255 Jun 20 02:54 /dev/nvidiactl



尝试运算服务后的结果:
2017-10-23 10:14:25.172617: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:0) -> (device: 0, name: Tesla M40 24GB, pci bus id: 0000:06:00.0)

通过上述测试用例,就可以跑通官方tensorflow gpu镜像。

NOTES: > GPUs 只能通过limits选项指定 > GPUs 是严格隔离的,不同容器之间不能共享 > 每个容器可以请求一个或者多个GPUS > GPUs 只能正整数级请求 > 使用K8S调度GPU容器必须开启privileged模式(识别宿主机设备)