Kubernetes(k8s) v1.16とNvidia-Docker2を用いたマルチノードDeepLearning環境の構築

この記事は近畿大学 Advent Calendar 2019 - Qiita3日目の記事です.

はじめに

今回はDeepLearningをするために研究室内にある複数の機械学習用サーバを簡単に管理・運用する際Kubernetes,Nvidia-Docker2,NVIDIA-device-plugin-for-Kubernetesを用いて環境構築を行いました.

Kubernetesの構築に関するブログ記事やQiitaなどは多数出ていますが,GPUを用いたKubernetes環境の構築に関する日本語のブログ記事やQiitaなどがあまり 見受けられませんでしたので,自分用のメモとして残しておきたいと思います.以下のブログを参考にさせていただきました.

blog.sky-net.pw

この記事の最終目標は以下の図のような構成となっております.この記事では右側黄色の波線で囲われたKubernetes Clusterの構築を行っていきたいと思います. 左側のVScodeとの連携に関する記事はまた別に書く予定です.

final_reach
最終目標図

KubernetesNvidia-Docker2・NVIDIA-device-plugin-for-Kubernetes

Kubernetes

Kubernetes(クーベネティス)とは雑に言ってしまうとコンテナオーケストレーションエンジンと言う,マルチホスト・マルチコンテナを管理・運用するためのツールです. 詳細は過去記事に書いたのでそちらを読むか,書籍などで知識を身につけてきてください.本記事ではある程度Docker・Kubernetesについての知識があることを前提で書いておりますので,Kubernetesの仕組みや機能の解説などは行いません.

tenzen.hatenablog.com

Nvidia-Docker2

Nvidia-Docker2はDockerと組み合わせて使用する,Nvidia社が公開しているOSSのことです.

Dockerのみの場合,ホストOSにインストールしているNvidia DriverやCUDAのバージョンと同じバージョンのものをDockerコンテナ内にインストールしてあげればDockerコンテナ内でもGPUを使うことができます.

しかしながら,完全にホスト依存になってしまうためGPUにおいてはコンテナの利便性が損なわれてしまいます.

そこでNvidia-Docker2を用いるとホストOS側のNvidia DriverやCUDAのバージョンにとらわれる事なく,コンテナ内に好きなバージョンのNvidia DriverやCUDAをインストールすることができます.

追記として記事執筆段階でKubernetesがサポートしている最新のDockerバージョンは18.09ですが,Dockerバージョン19.03(Kubernetesでのサポートはされていない.)からはNvidia GPUをネイティブサポートしたためDockerのみでNvidia-Docker2を使用した場合と同じようにGPUコンテナを作成できるようになっています.

NVIDIA-device-plugin-for-Kubernetes

NVIDIA-device-plugin-for-KubernetesNVIDIA社が提供しているKubernetesプラグインです. Git hub公式にも書いてありますが,以下のような機能があります.

github.com

今回は記事執筆時点で公開されている最新版のBeta4を使用します.

Kubernetes環境の構築

今回構築に使用するコンピュータの構成は以下のようになります. 構築の説明では以下のHOST名を用いて説明を行っていきますので,適宜参照してください.また,Workerでの構築説明では基本的にUtahaを使用して行っています.Workerがマルチホストになっている場合Utahaと同じ手順を全てのWorkerに対して行えば構築ができると思います.

・Master

HOST名 CPU メモリ GPU OS
Master intel Core i7 3770 8GB(Non ECC) なし Ubuntu 16.04LTS

・Worker

HOST名 CPU メモリ GPU OS
Eriri Intel Core i9 9900K 128GB(Non ECC) Nvidia Geforce RTX 2080ti 2台 Ubuntu 16.04LTS
Megumi Intel Core i9 9900K 128GB(Non ECC) Nvidia Geforce RTX 2080ti 2台 Ubuntu 16.04LTS
Utaha Xeon Bronze 3104 2socket 192GB(Ecc Registered) Nvidia Quadro GP100 4台 Ubuntu16.04LTS

Step01[全Node共通事項] : 準備

この章では各種ソフトウェアをインストールする前の準備を行います. 明記しない限り全てのWorker,Masterで同じ設定を行ってください.

スワップ機能のオフ

Kubernetesは,スワップ機能に対応していないためスワップ機能をオフにする必要があります.

そこで以下のコマンドを実行してスワップ機能のオンオフを確認してください.

$ cat /etc/fstab

# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
# / was on /dev/sda2 during installation
UUID=hoge hoge hoge /               ext4    errors=remount-ro 0       1
# /boot/efi was on /dev/sda1 during installation
UUID=hoge hoge  /boot/efi       vfat    umask=0077      0       1
swap was on /dev/sda3 during installation
#UUID=hoge hoge hoge none            swap    sw              0       0

上記のように「swap was on ~」の部分がコメントアウトされていない場合は,スワップ機能がオンになっているためコメントアウトしてスワップ機能をオフにしてください.その後以下のコマンドを実行してスワップがオフになっているか確認してください.

$ swapon

何か表示された場合は以下のコマンドを実行してスワップを削除します.

$ swapoff -a

ポートの開放

次に,kubernetesが使用するポートを開放していきます.

開放しなければならないポートはKubernetesの公式インストールガイドにも書いてあります.

https://kubernetes.io/ja/docs/setup/independent/install-kubeadm/kubernetes.io kubernetes.io

今回はufwを用いてポート制御を行うためufwをオンにし,確認を行ってください.

$ sudo ufw enable
$ sudo ufw status
Status: active

次に,Kubernetes公式インストールガイドにしたがって下記のポートを開けていきます.

また今回はflannelを用いて内部ネットワークを構築するため,以下のように他のポートも開けていきます. kubernetes.io

Node ポート番号
Master 53, 6443, 2379-2380, 8285, 8472, 9153, 10250-10252
Worker 53, 8285, 8472, 9153, 10250, 30000-32767

Master

$ sudo ufw allow 53
$ sudo ufw allow 6443
$ sudo ufw allow 2379:2380/tcp
$ sudo ufw allow 8285
$ sudo ufw allow 8472
$ sudo ufw allow 9153
$ sudo ufw allow 10250:10252/tcp

Worker

$ sudo ufw allow 53
$ sudo ufw allow 8285
$ sudo ufw allow 8472
$ sudo ufw allow 9153
$ sudo ufw allow 10250
$ sudo ufw allow 30000:32767

name serverの変更

最後にdnsmasqが悪さをしてgithubのissuesのようにKubernetesのCoreDNSを壊したり, github.com

コンテナ内から通信できなくなったりするためname serverを変更します.

まず,以下のコマンドを実行して設定変更の必要があるかどうかを確認します.

$ cat /etc/resolv.conf 
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 127.0.1.1

上記のようにnameserverが「127.0.1.1」になっている場合は設定変更の必要があります.それ以外の場合は以下の作業を行わなくても大丈夫なはずです.

まず下記のコマンドでNetworkManager.confを表示させ,「dns=dnsmasq」の部分がコメントアウトになっているか確認してください. コメントアウトされていない場合は「dns=dnsmasq」の部分をコメントアウトしてください.

その後ネットワークマネージャを再起動し,resolve.confを再確認してnameserverが変わっていることを確認してください.その際127.0.1.1の設定が残っている場合コメントアウトするか,削除してください.

$ cat /etc/NetworkManager/NetworkManager.conf 
[main]
plugins=ifupdown,keyfile,ofono
dns=dnsmasq

[ifupdown]
managed=false
$ sudo systemctl restart network-manager
$ cat /etc/resolv.conf 
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver xxx.xxx.xxx.xxx

以上で準備は完了です.

Step02[全Node共通事項]:ソフトウェアのインストール

この章では各種ソフトウェアのインストールを行います. 明記しない限り全てのWorker,Masterで同じ設定を行ってください. また本記事では以下の記事に書いたような理由で,Pod作成時も「kubectl create」ではなく「kubectl apply」を使用します.

tenzen.hatenablog.com

Nvidia Driver

今回は最新のNvidia Driverの中でも比較的安定しているnvidia-418をインストールしました.

Nvidia DriverはWorker Nodeにのみインストールしてください.(Master NodeにもGPUを積んでWorker Nodeとして使用する場合はMaster Nodeにもインストールしてください.)

下記コマンドを順番に実行します.

$ sudo add-apt-repository ppa:graphics-drivers/ppa
$ sudo apt-get update
$ sudo apt-get upgrade
$ reboot
$ apt-cache search nvidia-\d+
$ sudo apt-get install nvidia-418
$ reboot

Docker

次にDockerをインストールします. 以下のコマンドを順番に実行していけばインストールできます. 途中,「sudo apt-cache policy docker-ce」コマンドの出力結果に「docker-ce=18.06.3~ce~3-0~ubuntu」があることを確認してください. 大抵の場合最新版のdockerはKubernetesでサポートされてませんが,バージョン指定せずにdockerをインストールしてしまうと最新版がインストールされてしまうため,dockerのバージョンはサポートされているバージョンから選びましょう.今回は18.06.3を選択しました.

$ sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
$ sudo apt-get update
$ sudo apt-cache policy docker-ce
$ sudo apt-get install -y docker-ce=18.06.3~ce~3-0~ubuntu

ここで,指定バージョンのdockerがインストールできたかの確認をしてPC起動時にdockerが起動するよう設定しておきます.

$ docker -v
Docker version 18.06.3-ce, build d7080c1
$ service docker start && service docker status

Nvidia-Docker2

さらにNvidia-Docker2のインストールをしていきます.

Nvidia-Docker2はWorkerにのみインストールしてください.(MasterにもGPUを積んでWorkerとして使用する場合はMasterにもインストールしてください.)

よく似た名前でNvidia-Dockerと言うものがありますが,別物なので間違ってインストールしないように気をつけてください.

以下のコマンドを順番に実行していきます.

$ curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
$ curl -s -L https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
$ sudo apt-get update
$ sudo apt-get install -y nvidia-docker2
$ pkill -SIGHUP dockerd

これでNvidia-docker2のインストールは完了しました.

インストールが完了したら,nvidia-docker2のバージョン確認とdockerのランタイムをnvidia-docker2のものへ変更します.

まず,以下のコマンドでnvidia-docker2のバージョンを確認し,nvidia-dockerではなくnvidia-docker2がインストールできているか確認します.

$ sudo nvidia-docker version
NVIDIA Docker: 2.2.2
Client:
 Version:           18.06.3-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        d7080c1
 Built:             Wed Feb 20 02:27:18 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.06.3-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.3
  Git commit:       d7080c1
  Built:            Wed Feb 20 02:26:20 2019
  OS/Arch:          linux/amd64
  Experimental:     false

次にdockerのランタイムをnvidiaのものへ変更をしていきます.

下記のコマンドを実行してnvidiaランタイムをダウンロード後,/etc/docker/daemon.jsonnvidiaランタイムに変更します.

$ sudo apt-get install nvidia-container-runtime
$ sudo tee /etc/docker/daemon.json <<EOF
{
    "default-runtime": "nvidia",
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",
            "runtimeArgs": []
        }
    }
}
EOF
$ sudo pkill -SIGHUP dockerd
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

最後にnvidiaのランタイムが正しく読み込まれているかの確認を行います. 下記コマンドを実行後,GPUの情報が表示されれば正しく読み込まれています.

$ sudo docker run --rm nvidia/cuda nvidia-smi
Sun Nov 28 01:08:22 2019           
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.56       Driver Version: 418.56       CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Quadro GP100        Off  | 00000000:3B:00.0  On |                  Off |
| 28%   44C    P0    32W / 235W |    324MiB / 16276MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Quadro GP100        Off  | 00000000:5E:00.0 Off |                  Off |
| 26%   41C    P0    31W / 235W |      2MiB / 16278MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   2  Quadro GP100        Off  | 00000000:86:00.0 Off |                  Off |
| 26%   40C    P0    25W / 235W |      2MiB / 16278MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   3  Quadro GP100        Off  | 00000000:D8:00.0 Off |                  Off |
| 26%   39C    P0    25W / 235W |      2MiB / 16278MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+

Kubernetes

最後にKubernetesをインストールしていきます.何もツールを用いずインストールするを行うのは非常に難易度が高いため,今回はKubernetes公式もオススメしているkubeadmを用いてインストールを行っていきます.

kubernetes.io

以下のコマンドを実行してGoogleリポジトリの登録を行い,その後パッケージのインストールを行います.

$ sudo apt-get update
$ sudo apt-get update && sudo apt-get install -y apt-transport-https
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
#kubernetes用アプトゲットデータ取得元リストの登録
$ sudo cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
$ sudo apt-get update
$ sudo apt-get install -y kubectl kubelet kubeadm

もし,kubernetes用アプトゲットデータ取得元リストの登録で「権限がありません.」などのエラーが出た場合は以下のようにしてください.

$ sudo vim /etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main

Step03 : Masterのセットアップ

Step01でスワップ機能をオフにしましたが,念のためにもう一度オフにしておきます. その後,kubeadmを用いてセットアップしていきます. 「sudo kubeadm init」コマンドのオプション「----pod-network-cidr」は,クラスタ内ネットワークの構築にFlannelを使用する場合の設定です. 他の方法で構築する場合は以下のKubernetes公式ページを参考に設定してください.

kubernetes.io

kubeadmでのセットアップ

$ sudo swapoff -a
$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16
[init] Using Kubernetes version: v1.16.3
[preflight] Running pre-flight checks
    [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Activating the kubelet service
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [Master kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.21.159]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [Master localhost] and IPs [xxx.xxx.xxx.xxx 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [Master localhost] and IPs [xxx.xxx.xxx.xxx 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 16.506152 seconds
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.16" in namespace kube-system with the configuration for the kubelets in the cluster
[upload-certs] Skipping phase. Please see --upload-certs
[mark-control-plane] Marking the node Master as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node Master as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: hogehoge.hogehogehoge
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge.hogehogehoge \
    --discovery-token-ca-cert-hash sha256:hogehogehoge

下の方に「Your Kubernetes control-plane has initialized successfully!」と表示されれば成功です. このMasterが管理するKubernetes ClusterにWorkerを追加する際使用するtokenなので,一番下の 「kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge.hogehogehoge --discovery-token-ca-cert-hash sha256:hogehogehoge」をどこかに保存しておいてください. このtokenの有効期限は24時間のため有効期限が切れた場合は以下のコマンドで生成済みのtokenの有無を確認の上新規発行を行ってください.

$ sudo kubeadm token list
TOKEN                     TTL       EXPIRES                     USAGES                   DESCRIPTION                                                EXTRA GROUPS
hogehogehoge   23h       2019-11-29T20:53:35+09:00   authentication,signing   The default bootstrap token generated by 'kubeadm init'.   system:bootstrappers:kubeadm:default-node-token
$ sudo kubeadm create --print-join-command

ここで設定ミスをして,もう一度「sudo kubeadm init」コマンドを実行する場合「sudo kubeadm reset」を実行してkubeadmの設定をリセットしてから再度「sudo kubeadm init」コマンドを実行してください.

次に,kubectlで使用する認証ファイルの準備を行うため、以下のコマンドを実行してください.

$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

Kubernetes Cluster内ネットワークの設定

本記事では,Kubernetes Cluster内ネットワークの構築にFlannelを使用します. Kubernetes Cluster内ネットワークの詳細は改めて記事にします.

以下のコマンドを実行していき,Flannelのデプロイを行います.

$ cd
#作業用ディレクトリを作成する.
$ mkdir ./k8s
$ cd ./k8s
$ wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
--2019-11-28 23:59:89--  https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
raw.githubusercontent.com (raw.githubusercontent.com) をDNSに問いあわせています... 151.101.228.133
raw.githubusercontent.com (raw.githubusercontent.com)|151.101.228.133|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 14416 (14K) [text/plain]
`kube-flannel.yml' に保存中

kube-flannel.yml                        100%[===============================================================================>]  14.08K  --.-KB/s    時間 0.007s

2019-11-28 23:59:99 (1.91 MB/s) - `kube-flannel.yml' へ保存完了 [14416/14416]
$ kubectl apply -f kube-flannel.yml
podsecuritypolicy.policy/psp.flannel.unprivileged created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds-amd64 created
daemonset.apps/kube-flannel-ds-arm64 created
daemonset.apps/kube-flannel-ds-arm created
daemonset.apps/kube-flannel-ds-ppc64le created
daemonset.apps/kube-flannel-ds-s390x created
$ service kubelet restart

念の為に正しく設定できているか確認しておきます.

$ sudo kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                            READY   STATUS    RESTARTS   AGE     IP               NODE    NOMINATED NODE   READINESS GATES
kube-system   coredns-5644d7b6d9-48spc        1/1     Running   0          46m     10.244.0.2       Master   <none>           <none>
kube-system   coredns-5644d7b6d9-q924l        1/1     Running   0          46m     10.244.0.4       Master   <none>           <none>
kube-system   etcd-k8s-m                      1/1     Running   0          46m     xxx.xxx.xxx.xxx   Master   <none>           <none>
kube-system   kube-apiserver-k8s-m            1/1     Running   0          45m     xxx.xxx.xxx.xxx   Master   <none>           <none>
kube-system   kube-controller-manager-k8s-m   1/1     Running   0          45m     xxx.xxx.xxx.xxx   Master   <none>           <none>
kube-system   kube-flannel-ds-amd64-g99fj     1/1     Running   0          5m59s   xxx.xxx.xxx.xxx   Master   <none>           <none>
kube-system   kube-proxy-nc5wf                1/1     Running   0          46m     xxx.xxx.xxx.xxx   Master   <none>           <none>
kube-system   kube-scheduler-k8s-m            1/1     Running   0          46m     xxx.xxx.xxx.xxx   Master   <none>           <none>

ここで,すべてのサービスのSTATUSがRunningになっていることを確認してください. 特にcorednsが正しく起動できていない場合,Step01の設定が正しく行えていないことが考えられるためよく確認してください.

Step04 : Workerのセットアップ

Workerの方も,念のためにもう一度スワップ機能をオフにしておきます. その後Masterで発行されて保存したtokenを入力します.

$ sudo swapoff -a
$ sudo kubeadm join xxx.xxx.xxx.xxx:6443 --token hoge.hogehoge --discovery-token-ca-cert-hash sha256:hogehogehoge
[preflight] Running pre-flight checks
    [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet-start] Downloading configuration for the kubelet from the "kubelet-config-1.16" ConfigMap in the kube-system namespace
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Activating the kubelet service
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

「Run 'kubectl get nodes' on the control-plane to see this node join the cluster.」と最後に表示されればさきほど作成したKubernetes Clusterへの追加が成功しています.

Step05:最終確認

最後に,追加したWorkerがKubernetes Clusterに追加されたか確認を行うためMasterで以下のコマンドを実行してください.

$ kubectl get nodes
NAME    STATUS   ROLES    AGE   VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
Eriri   Ready    <none>   63m   v1.16.3   xxx.xxx.xxx.xxx   <none>        Ubuntu 16.04.6 LTS   4.4.0-166-generic   docker://18.6.3
Megumi   Ready    <none>   42m   v1.16.3   xxx.xxx.xxx.xxx   <none>        Ubuntu 16.04.6 LTS   4.4.0-166-generic   docker://18.6.3
Utaha   Ready    <none>   21m   v1.16.3   xxx.xxx.xxx.xxx   <none>        Ubuntu 16.04.6 LTS   4.4.0-166-generic   docker://18.6.3
Master   Ready    master   95m   v1.16.3   xxx.xxx.xxx.xxx   <none>        Ubuntu 16.04.6 LTS   4.4.0-169-generic   docker://18.6.3
$ 

このように追加したMaster・Workerがすべて表示されていれば,Kubernetesのセットアップは完了です. いよいよ次の章から,機械学習環境の構築を行っていきます.

Kubernetes Cluster上でのDeepLearning環境の構築

この章では,先ほど構築したKubernetes Cluster上に機械学習環境の構築を行っていきます. 作業場所は都度指定していますので,指定場所で実行してください.

NVIDIA-device-plugin-for-Kubernetesの導入

NVIDIA-device-plugin-for-Kubernetesのデーモンセットをデプロイします.以下はMasterで行ってください.

$ cd k8s
$ wget https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/1.0.0-beta4/nvidia-device-plugin.yml
--2019-11-28 23:11:44--  https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/1.0.0-beta4/nvidia-device-plugin.yml
raw.githubusercontent.com (raw.githubusercontent.com) をDNSに問いあわせています... 151.101.228.133
raw.githubusercontent.com (raw.githubusercontent.com)|151.101.228.133|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 2320 (2.3K) [text/plain]
`nvidia-device-plugin.yml' に保存中

nvidia-device-plugin.yml            100%[===================================================================>]   2.27K  --.-KB/s    時間 0s    

2019-11-28 23:11:44 (33.3 MB/s) - `nvidia-device-plugin.yml' へ保存完了 [2320/2320]
$ kubectl apply -f nvidia-device-plugin.yml
daemonset.apps/nvidia-device-plugin-daemonset created

次にNVIDIA-device-plugin-for-Kubernetesデーモンセットが正常にデプロイできているかどうか確認します.「nvidia-device-plugin-daemonset-xxxxx」のSTATUSがRUNNINGになっていれば正常にデプロイできています. また,デプロイしたNODEもWorkerの場所になっているかどうか確認してください.

$ kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                                   READY   STATUS    RESTARTS   AGE     IP               NODE    NOMINATED NODE   READINESS GATES
kube-system   coredns-5644d7b6d9-48spc               1/1     Running   0          148m    10.244.0.2       Master   <none>           <none>
kube-system   coredns-5644d7b6d9-q924l               1/1     Running   0          148m    10.244.0.4       Master   <none>           <none>
kube-system   etcd-k8s-m                             1/1     Running   0          147m    xxx.xxx.xxx.xxx   Master   <none>           <none>
kube-system   kube-apiserver-k8s-m                   1/1     Running   0          147m    xxx.xxx.xxx.xxx   Master   <none>           <none>
kube-system   kube-controller-manager-k8s-m          1/1     Running   0          147m    xxx.xxx.xxx.xxx   Master   <none>           <none>
kube-system   kube-flannel-ds-amd64-g99fj            1/1     Running   0          107m    xxx.xxx.xxx.xxx   Master   <none>           <none>
kube-system   kube-flannel-ds-amd64-r92b4            1/1     Running   2          85m     xxx.xxx.xxx.xxx   Utaha   <none>           <none>
kube-system   kube-proxy-7fvl8                       1/1     Running   0          85m     xxx.xxx.xxx.xxx   Utaha   <none>           <none>
kube-system   kube-proxy-nc5wf                       1/1     Running   0          148m    xxx.xxx.xxx.xxx   Master   <none>           <none>
kube-system   kube-scheduler-k8s-m                   1/1     Running   0          147m    xxx.xxx.xxx.xxx   Master   <none>           <none>
kube-system   nvidia-device-plugin-daemonset-2bwxt   1/1     Running   0          6m37s   10.244.1.2       Utaha   <none>           <none>

次にDocker hubからイメージを取得してきます.以下はWorkerで実行してください.

$ docker pull nvidia / k8s-device-plugin:1.0.0-beta4
$ sudo docker run --security-opt=no-new-privileges --cap-drop=ALL --network=none -it -v /var/lib/kubelet/device-plugins:/var/lib/kubelet/device-plugins nvidia/k8s-device-plugin:1.0.0-beta4
2019/12/1 05:18:26 Loading NVML
2019/12/1 05:18:26 Fetching devices.
2019/12/1 05:18:26 Starting FS watcher.
2019/12/1 05:18:26 Starting OS watcher.
2019/12/1 05:18:26 Starting GRPC server
2019/12/1 05:18:26 Starting to serve on /var/lib/kubelet/device-plugins/nvidia.sock
2019/12/1 05:18:27 Registered device plugin with Kubelet

セットアップが完了したので,テスト用Podをデプロイして確認します. デプロイを行うのでもちろん以下の作業はMasterで行ってください. デプロイするPodの中身は自分でマニフェストファイルを書くか,以下の内容で「sample.yaml」ファイルを作成してください.

piVersion: v1
kind: Pod
metadata:
  name: gpu-pod
spec:
  containers:
    - name: cuda-container
      image: nvidia/cuda:10.0-devel
      tty: true
      resources:
        limits:
          nvidia.com/gpu: 1 # requesting 1 GPUs

マニフェストが完成したら以下のコマンドを実行してgpu-podをデプロイし,デプロイしたPodのStatusがRunnningになっているかどうか確認します.

$ kubectl apply -f sample.yaml
pod/gpu-pod created
$ kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                                   READY   STATUS    RESTARTS   AGE   IP               NODE    NOMINATED NODE   READINESS GATES
default       gpu-pod                                1/1     Running   0          10s   10.244.1.5       Utaha   <none>           <none>
(省略)

以上でKubernetes Cluster上に基本的なのDeepLearning構築をすることができました.

DeepLearning環境Deploymentの作成

次に,構築したKubernetes Cluster上にDockerfileを用いてDeploymentを作成していきます. Dockerイメージで基本的なパッケージを準備し,そのイメージを使用して様々なホストにスケーリングが容易なDeploymentを構築していきます.先ほどの「sample.yaml」ではPodをデプロイしていましたが,本番環境になりますのでKubernetes推奨のDeploymentのデプロイしていきます.DeploymentやPodの違いの詳しい話は改めて記事にします.

hub.docker.com

Dockerイメージの準備

DockerfileではDeepLearningで使用しそうな以下のパッケージを内包しています.

  • curl
  • wget
  • git
  • unzip
  • imagemagick
  • bzip2
  • vim
  • pyenv
  • anaconda3-4.4.0
  • libsm6
  • cuda 10.0
  • cndnn 7.6.5
  • opencv-python 3.4.7.28
  • tensorflow-gpu 1.13.1
  • keras
  • torch
  • torchvision
  • libgl1-mesa-dev
  • tqdm
  • torchsummary
  • progressbar

上記のようなパッケージを内包するイメージを作成するため以下のようなDockerfileをMasterで作成してください.

FROM nvidia/cuda:10.0-cudnn7-devel-ubuntu16.04

#preparation
RUN apt-get update
RUN apt-get install -y curl wget git unzip imagemagick bzip2 vim
RUN git clone https://github.com/pyenv/pyenv.git .pyenv

WORKDIR /
ENV HOME  /
ENV PYENV_ROOT /.pyenv
ENV PATH $PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH

RUN pyenv install anaconda3-4.4.0
RUN pyenv global anaconda3-4.4.0
RUN pyenv rehash

RUN pip install --upgrade pip
RUN apt-get update && apt-get install -y libsm6
RUN pip install opencv-python==3.4.7.28
RUN pip install tensorflow-gpu==1.13.1 --ignore-installed --user
RUN pip install keras  
RUN pip install torch torchvision && apt-get install -y libgl1-mesa-dev
RUN pip install tqdm
RUN pip install torchsummary
RUN pip install progressbar

Dockerfileをビルドします. しばらく時間がかかるので待ちましょう.

$ sudo docker build -t dl_env .
Sending build context to Docker daemon  45.06kB
Step 1/20 : FROM nvidia/cuda:10.0-cudnn7-devel-ubuntu16.04
10.0-cudnn7-devel-ubuntu16.04: Pulling from nvidia/cuda
976a760c94fc: Pull complete 
c58992f3c37b: Pull complete 
0ca0e5e7f12e: Pull complete 
f2a274cc00ca: Pull complete 
708a53113e13: Pull complete 
465b2edc87fb: Pull complete 
4189f57a58ef: Pull complete 
35de2d1091bb: Pull complete 
719d77537fdc: Pull complete 
3745e7bcc1b3: Pull complete 
Digest: sha256:6cd48444de35a2aa8fa8652da86769205f6e895167d304537403e169bcee1fd8
Status: Downloaded newer image for nvidia/cuda:10.0-cudnn7-devel-ubuntu16.04
 ---> 7bb1f0b039e1
Step 2/20 : RUN apt-get update
 ---> Running in 35fe3e24dc9d
(省略)
Successfully built b0eeafa8df6d
Successfully tagged dl_env:latest

次に,レジストリに作成したイメージをアップロードします. Docker hubのようなサービスを利用するか,プライベートレジストリを作成してそれを利用してください.今回は,プライベートレジストリを使用します.プライベートレジストリの構築については過去に記事に書いたのでそちらを参照してください.今回はMaster上でレジストリコンテナをデプロイしています.

tenzen.hatenablog.com

docker hubにアップロードしたい場合はあらかじめdocker hubのアカウントを取得後,Masterで以下のようなコマンドを実行し, ログインしておいてください.

$ sudo docker login

また,docker hubを使用する場合下記のアップロードの方法やタグのつけ方など若干違いますので,注意してください.

作成しておいたイメージにタグをつけてからレジストリにアップロードを行います. 今回は先ほどビルドした「dl_env」をレジストリにアップロードします.

$ docker images
REPOSITORY                              TAG                             IMAGE ID            CREATED             SIZE
dl_env                                  latest                          b0eeafa8df6d        15 hours ago        9.3GB
nvidia/cuda                             10.0-cudnn7-devel-ubuntu16.04   7bb1f0b039e1        2 days ago          3.04GB
k8s.gcr.io/kube-proxy                   v1.16.3                         9b65a0f78b09        2 weeks ago         86.1MB
k8s.gcr.io/kube-apiserver               v1.16.3                         df60c7526a3d        2 weeks ago         217MB
k8s.gcr.io/kube-controller-manager      v1.16.3                         bb16442bcd94        2 weeks ago         163MB
k8s.gcr.io/kube-scheduler               v1.16.3                         98fecf43a54f        2 weeks ago         87.3MB
k8s.gcr.io/etcd                         3.3.15-0                        b2756210eeab        2 months ago        247MB
k8s.gcr.io/coredns                      1.6.2                           bf261d157914        3 months ago        44.1MB
quay.io/coreos/flannel                  v0.11.0-amd64                   ff281650a721        10 months ago       52.6MB
k8s.gcr.io/pause                        3.1                             da86e6ba6ca1        23 months ago       742kB
konradkleine/docker-registry-frontend   v2                              60d4b91e68fa        2 years ago         266MB
registry                                2.3.0                           5eaced67751b        3 years ago         166MB
$ sudo docker tag dlenv:latest xxx.xxx.xxx.xxx:5000/k8s/dl_env:hoge
$ sudo docker push xxx.xxx.xxx.xxx:5000/k8s/dl_env:hoge
The push refers to repository [xxx.xxx.xxx.xxx:5000/k8s/dl_env]
e428433c3f7c: Pushed 
334a36d32e08: Pushed 
02329ea76a91: Pushed 
e3078c762ee2: Pushed 
aa04522b497b: Pushed 
11966f162c9e: Pushed 
78b5aab0476b: Pushed 
f8f213d08b00: Pushed 
f8dda259a125: Pushed 
5159c055acb8: Pushed 
b8d56ef47379: Pushed 
4b894dd79dd5: Pushed 
1f8956b49964: Pushed 
9154b69eed62: Pushed 
33d281c6c667: Pushed 
ca1122f45652: Pushed 
585032098094: Pushed 
cb0856d33862: Pushed 
03a1cbfdd831: Pushed 
e579c4e796e4: Pushed 
b7ee80f86be3: Pushed 
aa7f8c8d5f39: Pushed 
48817fbd6c92: Pushed 
1b039d138968: Pushed 
7082d7d696f8: Pushed 
hoge: digest: sha256:hogehogehogehogehoge size: 5579

マニフェストの準備

先ほども言いましたが,本記事ではDeploymentを使用して環境構築をしていきます. 以下に私が準備したマニフェストgpu-deployment.yamlを使用するか,自分で作成した物を使用してください.また,以下全てMasterで行ってください.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gpu-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gpu-dlenv
  template:
    metadata:
      labels:
        app: gpu-dlenv
    spec:
      containers:
        - name: gpu-dlenv-container
          image: xxx.xxx.xxx.xxx:5000/k8s/dl_env:hoge

#デプロイするNodeにのみ存在するimageを使用する場合は下記を記述する.
#          imagePullPolicy: Never

          tty: true
          volumeMounts:
          - mountPath: /srv
            name: host-share
          resources:
            limits:
              nvidia.com/gpu: 2 #GPUの個数を制限する.
      volumes:
      - name: host-share
        hostPath:
          path: /host_hoge
          type: DirectoryOrCreate
#nodeを指定す場合は下記のように記述する. 
#      nodeSelector:
#          type: <label名>

今回作成するDeploymentではReplica数を1にしています.これによりオートヒーリング機能が使用可能になり,何らかの問題でホストに障害が発生した場合でも別ホストにて同じDeploymentを自動で作成してくれます.

spec.template.spec.containers.imageでは先ほどのPrivate Docker RegistryにアップロードしたImageを指定しています.その他使用したいイメージを指定することができますが,使用するイメージがNode上にしか存在しない物を使用する際はspec.template.spec.containers.imagePullPolicyをNeverに指定してください.これを指定しないとデプロイする際エラーを吐いてDeploymentの作成をすることができません.

Volumeについては共有ドライブとしてNFSサーバなどを建ててpersistentVolumeをマウントするべきですが,私の環境はネットワークが貧弱でDeepLearningに使用する大量の学習データなどをネットワーク経由で転送することが難しいためホストのドライブをマウントするように設定しています.例ではspec.template.spec.containers.volumeMountsでPod側の場所を,spec.template.spec.volumes.hostPath.pathでHost側の場所を指定していて,Pod内の/srvにHostの/host_hogeをマウントしています.

さらに例では条件に合う計算リソースを自動選択してDeploymentを作成するようにしていますが,spec.template.spec.nodeSelectorでホストを指定することでDeploymentを作成するホストを強制することもできます.

デプロイ

全ての準備が完了したのでDeploymentを作成していきます.以下のコマンドをMasterで実行してください.

$ kubectl apply -f gpu-deployment.yaml --record
deployment.apps/gpu-deployment created

上記コマンドでは「--record」オプションをつけることでアップデート履歴を保持できるようにしています.次にdeployment・ReplicaSet・Podが正常に作成できているかどうか確認します.

$ kubectl get deployments --all-namespaces -o wide
NAMESPACE     NAME             READY   UP-TO-DATE   AVAILABLE   AGE    CONTAINERS            IMAGES                                SELECTOR
default       gpu-deployment   1/1     1            1           4m4s   gpu-dlenv-container   xxx.xxx.xxx.xxx:5000/k8s/dl_env:hoge   app=gpu-dlenv
kube-system   coredns          2/2     2            2           3d4h   coredns               k8s.gcr.io/coredns:1.6.2              k8s-app=kube-dns
$
$
$ kubectl get replicasets --all-namespaces -o wide
NAMESPACE     NAME                        DESIRED   CURRENT   READY   AGE     CONTAINERS            IMAGES                                SELECTOR
default       gpu-deployment-7ffd6c86d5   1         1         1       7m44s   gpu-dlenv-container   xxx.xxx.xxx.xxx:5000/k8s/dl_env:hoge   app=gpu-dlenv,pod-template-hash=7ffd6c86d5
kube-system   coredns-5644d7b6d9          2         2         2       3d5h    coredns               k8s.gcr.io/coredns:1.6.2              k8s-app=kube-dns,pod-template-hash=5644d7b6d9
$
$
$ kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                                   READY   STATUS    RESTARTS   AGE     IP               NODE    NOMINATED NODE   READINESS GATES
default       gpu-deployment-7ffd6c86d5-q7gbg        1/1     Running   0          2m56s   10.244.1.29      utaha   <none>           <none>
(省略)

それぞれRunnnigになっていて正常に起動できていることがわかります.また以下のコマンドを実行して見ると先ほどPrivate Docker RegistryからアップロードしたイメージをWorkerにダウンロードできていることがわかります.pod以下には先ほど確認したPodの名前を入力してください.

$ kubectl describe pod gpu-deployment-7ffd6c86d5-q7gbg
(省略)
Events:
  Type    Reason     Age        From               Message
  ----    ------     ----       ----               -------
  Normal  Scheduled  <unknown>  default-scheduler  Successfully assigned default/gpu-deployment-7ffd6c86d5-q7gbg to utaha
  Normal  Pulling    15m        kubelet, utaha     Pulling image "xxx.xxx.xxx.xxx:5000/k8s/dl_env:hoge"
  Normal  Pulled     15m        kubelet, utaha     Successfully pulled image "xxx.xxx.xxx.xxx:5000/k8s/dl_env:hoge"
  Normal  Created    15m        kubelet, utaha     Created container gpu-dlenv-container
  Normal  Started    15m        kubelet, utaha     Started container gpu-dlenv-container

これで環境構築は完了しました.

コンテナの確認

次に,実際コンテナ内をみてDeepLearnig環境の構築ができているかどうか,Volumeが機能しているかどうかなどを確認していきます.

まず,以下のコマンドで擬似的にコンテナ内にログインします.今回はコンテナを一つしか作成していないため指定しませんが,一つのPod内に複数コンテナを作成した場合には「-c:<コンテナ名>」オプションで擬似ログインするコンテナを指定してください.以下明示しない限りMasterで行ってください.

$ kubectl exec -it gpu-deployment-7ffd6c86d5-q7gbg /bin/bash
root@gpu-deployment-7ffd6c86d5-q7gbg:/# nvidia-smi 
Sun Dec  1 17:52:33 2019       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.87.00    Driver Version: 418.87.00    CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Quadro GP100        Off  | 00000000:86:00.0 Off |                  Off |
| 26%   36C    P0    25W / 235W |      2MiB / 16278MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Quadro GP100        Off  | 00000000:D8:00.0 Off |                  Off |
| 26%   35C    P0    24W / 235W |      2MiB / 16278MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

root@gpu-deployment-7ffd6c86d5-q7gbg:/#python3
Python 3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:09:58) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2   
>>> cv2.__version__
'3.4.7'
>>>
>>> import tensorflow as tf 
>>> tf.__version__
'1.13.1'
>>>
>>> import keras
Using TensorFlow backend.
>>> keras.__version__
'2.3.1'
>>>
>>> torch.__version__
'1.3.1'
>>> 
>>> import torchvision
>>> torchvision.__version__
'0.4.2'
>>> exit()
root@gpu-deployment-7ffd6c86d5-q7gbg:/# 
root@gpu-deployment-7ffd6c86d5-q7gbg:/# conda -V
conda 4.3.21

上記のようにDockerfileで指定したパッケージがインストールされていることがわかります.また,GPUも2本認識されていることがわかります.

次にVolumeの確認を行っていきます.ホストの/host_hogeをPod内の/srvにマウントしたので,確認の前に/host_hogeにサンプルファイルを作成しておきます.以下の作業のみDeploymentが作成されたWorker上で行ってください.

$ cd /host_hoge
$ touch test.txt

次に,Workerホスト上で作成したファイルが/srvに反映されているかどうか確認します.

$ kubectl exec -it gpu-deployment-7ffd6c86d5-q7gbg /bin/bash
root@gpu-deployment-7ffd6c86d5-q7gbg:/# cd /srv
root@gpu-deployment-7ffd6c86d5-q7gbg:/srv# ls
host_test.txt
root@gpu-deployment-7ffd6c86d5-q7gbg:/srv#

最後に,GPUでtensorflow用のmnistサンプルを使って学習させてみます.

$ kubectl exec -it gpu-deployment-7ffd6c86d5-q7gbg /bin/bash
root@gpu-deployment-7ffd6c86d5-q7gbg:/# cd /srv
root@gpu-deployment-7ffd6c86d5-q7gbg:/srv# wget https://raw.githubusercontent.com/fchollet/keras/master/examples/mnist_cnn.py
--2019-12-01 18:42:06--  https://raw.githubusercontent.com/fchollet/keras/master/examples/mnist_cnn.py
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.228.133
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.228.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2257 (2.2K) [text/plain]
Saving to: 'mnist_cnn.py'

mnist_cnn.py              100%[=====================================>]   2.20K  --.-KB/s    in 0s      

2019-12-01 18:42:06 (35.2 MB/s) - 'mnist_cnn.py' saved [2257/2257]
root@gpu-deployment-7ffd6c86d5-q7gbg:/srv# python3 mnist_cnn.py 
Using TensorFlow backend.
/.local/lib/python3.6/site-packages/tensorflow/python/framework/dtypes.py:526: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_qint8 = np.dtype([("qint8", np.int8, 1)])
/.local/lib/python3.6/site-packages/tensorflow/python/framework/dtypes.py:527: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_quint8 = np.dtype([("quint8", np.uint8, 1)])
/.local/lib/python3.6/site-packages/tensorflow/python/framework/dtypes.py:528: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_qint16 = np.dtype([("qint16", np.int16, 1)])
/.local/lib/python3.6/site-packages/tensorflow/python/framework/dtypes.py:529: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_quint16 = np.dtype([("quint16", np.uint16, 1)])
/.local/lib/python3.6/site-packages/tensorflow/python/framework/dtypes.py:530: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_qint32 = np.dtype([("qint32", np.int32, 1)])
/.local/lib/python3.6/site-packages/tensorflow/python/framework/dtypes.py:535: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  np_resource = np.dtype([("resource", np.ubyte, 1)])
Downloading data from https://s3.amazonaws.com/img-datasets/mnist.npz
11493376/11490434 [==============================] - 13s 1us/step
x_train shape: (60000, 28, 28, 1)
60000 train samples
10000 test samples
WARNING:tensorflow:From /.local/lib/python3.6/site-packages/tensorflow/python/ops/resource_variable_ops.py:435: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.
Instructions for updating:
Colocations handled automatically by placer.
WARNING:tensorflow:From /.local/lib/python3.6/site-packages/tensorflow/python/ops/math_ops.py:3066: to_int32 (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.cast instead.
2019-12-01 18:45:08.955276: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 AVX512F FMA
2019-12-01 18:45:09.416060: I tensorflow/compiler/xla/service/service.cc:150] XLA service 0x5978410 executing computations on platform CUDA. Devices:
2019-12-01 18:45:09.416135: I tensorflow/compiler/xla/service/service.cc:158]   StreamExecutor device (0): Quadro GP100, Compute Capability 6.0
2019-12-01 18:45:09.416154: I tensorflow/compiler/xla/service/service.cc:158]   StreamExecutor device (1): Quadro GP100, Compute Capability 6.0
2019-12-01 18:45:09.421518: I tensorflow/core/platform/profile_utils/cpu_utils.cc:94] CPU Frequency: 1699985000 Hz
2019-12-01 18:45:09.422753: I tensorflow/compiler/xla/service/service.cc:150] XLA service 0x59ea1a0 executing computations on platform Host. Devices:
2019-12-01 18:45:09.422798: I tensorflow/compiler/xla/service/service.cc:158]   StreamExecutor device (0): <undefined>, <undefined>
2019-12-01 18:45:09.423150: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1433] Found device 0 with properties: 
name: Quadro GP100 major: 6 minor: 0 memoryClockRate(GHz): 1.4425
pciBusID: 0000:86:00.0
totalMemory: 15.90GiB freeMemory: 15.64GiB
2019-12-01 18:45:09.423319: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1433] Found device 1 with properties: 
name: Quadro GP100 major: 6 minor: 0 memoryClockRate(GHz): 1.4425
pciBusID: 0000:d8:00.0
totalMemory: 15.90GiB freeMemory: 15.64GiB
2019-12-01 18:45:09.426778: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1512] Adding visible gpu devices: 0, 1
2019-12-01 18:45:09.432828: I tensorflow/core/common_runtime/gpu/gpu_device.cc:984] Device interconnect StreamExecutor with strength 1 edge matrix:
2019-12-01 18:45:09.432890: I tensorflow/core/common_runtime/gpu/gpu_device.cc:990]      0 1 
2019-12-01 18:45:09.432909: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1003] 0:   N Y 
2019-12-01 18:45:09.432926: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1003] 1:   Y N 
2019-12-01 18:45:09.433319: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1115] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 15212 MB memory) -> physical GPU (device: 0, name: Quadro GP100, pci bus id: 0000:86:00.0, compute capability: 6.0)
2019-12-01 18:45:09.434461: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1115] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:1 with 15212 MB memory) -> physical GPU (device: 1, name: Quadro GP100, pci bus id: 0000:d8:00.0, compute capability: 6.0)
Train on 60000 samples, validate on 10000 samples
Epoch 1/12
2019-12-01 18:45:11.671905: I tensorflow/stream_executor/dso_loader.cc:152] successfully opened CUDA library libcublas.so.10.0 locally
60000/60000 [==============================] - 7s 121us/step - loss: 0.2637 - accuracy: 0.9186 - val_loss: 0.0531 - val_accuracy: 0.9823
Epoch 2/12
60000/60000 [==============================] - 4s 72us/step - loss: 0.0891 - accuracy: 0.9738 - val_loss: 0.0430 - val_accuracy: 0.9857
Epoch 3/12
60000/60000 [==============================] - 4s 70us/step - loss: 0.0662 - accuracy: 0.9801 - val_loss: 0.0315 - val_accuracy: 0.9891
Epoch 4/12
60000/60000 [==============================] - 4s 71us/step - loss: 0.0550 - accuracy: 0.9833 - val_loss: 0.0297 - val_accuracy: 0.9895
Epoch 5/12
60000/60000 [==============================] - 4s 69us/step - loss: 0.0462 - accuracy: 0.9859 - val_loss: 0.0295 - val_accuracy: 0.9900
Epoch 6/12
60000/60000 [==============================] - 4s 70us/step - loss: 0.0419 - accuracy: 0.9870 - val_loss: 0.0283 - val_accuracy: 0.9900
Epoch 7/12
60000/60000 [==============================] - 4s 71us/step - loss: 0.0363 - accuracy: 0.9884 - val_loss: 0.0266 - val_accuracy: 0.9903
Epoch 8/12
60000/60000 [==============================] - 4s 71us/step - loss: 0.0335 - accuracy: 0.9897 - val_loss: 0.0267 - val_accuracy: 0.9913
Epoch 9/12
60000/60000 [==============================] - 4s 70us/step - loss: 0.0317 - accuracy: 0.9900 - val_loss: 0.0311 - val_accuracy: 0.9893
Epoch 10/12
60000/60000 [==============================] - 4s 70us/step - loss: 0.0287 - accuracy: 0.9910 - val_loss: 0.0270 - val_accuracy: 0.9910
Epoch 11/12
60000/60000 [==============================] - 4s 71us/step - loss: 0.0286 - accuracy: 0.9911 - val_loss: 0.0285 - val_accuracy: 0.9912
Epoch 12/12
60000/60000 [==============================] - 4s 71us/step - loss: 0.0264 - accuracy: 0.9918 - val_loss: 0.0290 - val_accuracy: 0.9904
Test loss: 0.028990692665684947
Test accuracy: 0.9904000163078308
root@gpu-deployment-7ffd6c86d5-q7gbg:/srv# 

学習実行中に別ターミナルを開いて以下のコマンドを実行するとGPUで実行できていることが確認できます.

$ kubectl exec gpu-deployment-7ffd6c86d5-q7gbg -- watch -n1 "nvidia-smi"
Sun Dec  1 18:50:58 2019       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.87.00    Driver Version: 418.87.00    CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Quadro GP100        Off  | 00000000:86:00.0 Off |                  Off |
| 33%   48C    P0    95W / 235W |  15745MiB / 16278MiB |     45%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Quadro GP100        Off  | 00000000:D8:00.0 Off |                  Off |
| 26%   36C    P0    34W / 235W |    267MiB / 16278MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+

計算資源の可視化

metrics serverとKubernetes Dashboard2.0を用いてkubernetes cluster計算リソースの可視化を行っていきます.これにより監視がしやすくなります.以下の記事を参考にさせていただきました.また,この作業はMasterで行ってください.

qiita.com

metrics server

Kubernetesでの計算資源利用率の収集などはHeapsterが有名でしたが、サポートが終了することが決定しています.

github.com

RETIRED: Heapster is now retired. See the deprecation timeline for more information on support. We will not be making changes to Heapster.

そこで,今回はmetrics serverを使用していきます.

github.com

以下のコマンドを実行してmetrics serverをダウンロードしてきます.

$ git clone https://github.com/kubernetes-incubator/metrics-server.git

ダウンロードしてきたままではmetrics serverが正常に起動しないようなので下記のように「metrics-server/deploy/1.8+/metrics-server-deployment.yaml」を書き換えます.

github.com

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: metrics-server
  namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: metrics-server
  namespace: kube-system
  labels:
    k8s-app: metrics-server
spec:
  selector:
    matchLabels:
      k8s-app: metrics-server
  template:
    metadata:
      name: metrics-server
      labels:
        k8s-app: metrics-server
    spec:
      serviceAccountName: metrics-server
      volumes:
      # mount in tmp so we can safely use from-scratch images and/or read-only containers
      - name: tmp-dir
        emptyDir: {}
      containers:
      - name: metrics-server
        image: k8s.gcr.io/metrics-server-amd64:v0.3.6
        args:
          - --cert-dir=/tmp
          - --secure-port=4443
        ports:
        - name: main-port
          containerPort: 4443
          protocol: TCP
        securityContext:
          readOnlyRootFilesystem: true
          runAsNonRoot: true
          runAsUser: 1000
        imagePullPolicy: Always
+       command:
+       - /metrics-server
+       - --kubelet-insecure-tls
+       - --kubelet-preferred-address-types=InternalDNS,InternalIP,ExternalDNS,ExternalIP,Hostname
        volumeMounts:
        - name: tmp-dir
          mountPath: /tmp
      nodeSelector:
        beta.kubernetes.io/os: linux

これで正常に起動するはずなのでmetrics serverをデプロイします.

$ kubectl apply -f metrics-server/deploy/1.8+/
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created
serviceaccount/metrics-server created
deployment.apps/metrics-server created
service/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
$
$
$ kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                                   READY   STATUS    RESTARTS   AGE     IP               NODE    NOMINATED NODE   READINESS GATES
kube-system   metrics-server-795b774c76-7k6tp        1/1     Running   0          3m29s   10.244.1.30      utaha   <none>           <none>
(省略)

Workerでmetricsで起動していることを確認してください.Workerで起動していないと資源情報の収集が行えません.

Kubernetes Dashboard 2.0

Kubernetes Dashboard 1.xは紹介してるブログが多く,公式GithubのReadmeでもv1.xのインストール方法が書いてありますが,Kubernetes1.6では動作しないので注意してください.

github.com

本記事執筆段階でKubernetes Dashboard 2.0はまだBeta版しかリリースされておりません.今回は最新版のBeta6を使用して構築していきます.

$ wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta6/aio/deploy/recommended.yaml
--2019-12-02 05:30:40--  https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta6/aio/deploy/recommended.yaml
raw.githubusercontent.com (raw.githubusercontent.com) をDNSに問いあわせています... 151.101.228.133
raw.githubusercontent.com (raw.githubusercontent.com)|151.101.228.133|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 7568 (7.4K) [text/plain]
`recommended.yaml' に保存中

recommended.yaml                    100%[==================================================================>]   7.39K  --.-KB/s    時間 0s    

2019-12-02 05:30:41 (28.6 MB/s) - `recommended.yaml' へ保存完了 [7568/7568]

Serviceをデプロイする際 デフォルトではKubernetes Cluster内からしかアクセスできないようになっていますが,Kubernetes Cluster外からでもアクセスできるようにするためrecommend.yamlを書き換えます.

(省略)
---

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard

---

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
+ type: NodePort
  ports:
    - port: 443
      targetPort: 8443
+     nodePort: 30794
  selector:
    k8s-app: kubernetes-dashboard

---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-certs
  namespace: kubernetes-dashboard
type: Opaque

---
(省略)

以下のコマンドでKubernetes dashboard2.0をデプロイし,確認を行います.

$ kubectl apply -f recommended.yaml 
namespace/kubernetes-dashboard created
serviceaccount/kubernetes-dashboard created
service/kubernetes-dashboard created
secret/kubernetes-dashboard-certs created
secret/kubernetes-dashboard-csrf created
secret/kubernetes-dashboard-key-holder created
configmap/kubernetes-dashboard-settings created
role.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard created
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
deployment.apps/kubernetes-dashboard created
service/dashboard-metrics-scraper created
deployment.apps/dashboard-metrics-scraper created
$
$
$ kubectl get svc -n kubernetes-dashboard
NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)         AGE
dashboard-metrics-scraper   ClusterIP   10.104.216.215   <none>        8000/TCP        2m28s
kubernetes-dashboard        NodePort    10.110.229.237   <none>        443:30794/TCP   2m28s

kubernetes Dashboardの起動ができたのでブラウザで「<MasterのIPアドレス>:30794」にアクセスします. 以下のように表示されルと思うので,トークンを準備します.

k8s dashboard login page
Kubernetes Dashboard 2.0のログイン画面

以下のようなdashboard-admin-serviceaccount.yamlを作成します.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: dashboard-admin
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: dashboard-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: dashboard-admin
    namespace: kube-system

dashboard-admin-serviceaccountをデプロイします.

$ kubectl apply -f dashboard-admin-serviceaccount.yaml
serviceaccount/dashboard-admin created
clusterrolebinding.rbac.authorization.k8s.io/dashboard-admin created
$
$
$ kubectl get secret -n kube-system | grep admin
dashboard-admin-token-bvcfp                      kubernetes.io/service-account-token   3      6m2s

最後のコマンドで取得してきた名前のトークンを表示させます.

$ kubectl describe secret -n kube-system dashboard-admin-token-bvcfp
Name:         dashboard-admin-token-bvcfp
Namespace:    kube-system
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: dashboard-admin
              kubernetes.io/service-account.uid: 70c51d53-2424-48f7-8f1c-67b860541baa

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1025 bytes
namespace:  11 bytes
token:      hogehogehoge...

表示されたトークンをブラウザのトークンの所に貼り付けてログインします. 正常に動作していると下記のようなページ(下記はダークモードにしている.)が表示されるはずです.また,metrics serverの起動に多少時間がかかるためグラフの表示は少し待ちましょう.お疲れ様でした.

Complete Kubertes Dashboard 2.0
Kubernetes Dashboard 2.0 セットアップ完了後

最後に

これで基本的なKubernetesでのDeepLearning環境の構築は完了です. かなり長かったと思うので最後までやり遂げた人はお疲れ様でした. はじめにで述べたとおり,次の記事では今回構築したKubenetes環境とMacVScodeを使用してリモート開発環境の構築について書く予定ですのでよかったらそちらも読んでください.